├── .babelrc
├── .editorconfig
├── .eslintrc
├── .gitignore
├── README.md
├── build
├── webpack.base.conf.js
├── webpack.dev.conf.js
└── webpack.prod.conf.js
├── gulpfile.js
├── package.json
├── postcss.config.js
├── public
├── favicon.ico
└── index.html
├── server
├── app.js
├── config
│ ├── index.js
│ ├── mongoose.js
│ ├── statusCode.js
│ └── winston.conf.js
├── controllers
│ ├── user.controller.js
│ └── webIssues.controller.js
├── index.js
├── middleware
│ ├── authControl.js
│ └── checkOrigin.js
├── models
│ ├── commons
│ │ └── counters.js
│ ├── user.model.js
│ └── webIssues.model.js
├── routers
│ └── index.js
├── services
│ ├── user.service.js
│ └── webIssues.service.js
└── utils
│ ├── loghelper.js
│ ├── sender.js
│ └── util.js
├── src
├── entry
│ ├── index.dev.js
│ └── index.js
├── images
│ └── logo.svg
├── libs
│ └── index.js
├── routes
│ ├── Account
│ │ ├── account.scss
│ │ └── changePwd.js
│ ├── Auth
│ │ ├── auth.scss
│ │ ├── index.js
│ │ ├── login.js
│ │ ├── register.js
│ │ └── resetPwd.js
│ ├── IndexPage
│ │ └── index.js
│ ├── LayoutPage
│ │ └── index.js
│ ├── User
│ │ ├── detail.js
│ │ └── list.js
│ ├── index.js
│ ├── index.x.js
│ └── routes.js
├── scss
│ ├── animation.scss
│ └── global.scss
├── utils
│ ├── fetcher.js
│ ├── storage.js
│ ├── util.js
│ └── xfetch.js
└── views
│ └── index.html
├── webpack.config.js
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | ["es2015", {"modules": false}],
4 | "stage-0",
5 | "react"
6 | ],
7 | "plugins": [
8 | "react-hot-loader/babel",
9 | ["import", { "libraryName": "antd", "style": "css", "css": "true" }]
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | indent_style = tab
6 | indent_size = 4
7 | end_of_line = lf
8 | charset = utf-8
9 | trim_trailing_whitespace = true
10 | insert_final_newline = true
11 |
12 | [*.md]
13 | trim_trailing_whitespace = false
14 |
15 | [Makefile]
16 | indent_style = tab
17 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "parser": "babel-eslint",
3 | "extends": "airbnb",
4 | "rules": {
5 | "generator-star-spacing": [0],
6 | "consistent-return": [0],
7 | "react/forbid-prop-types": [0],
8 | "react/jsx-filename-extension": [1, { "extensions": [".js"] }],
9 | "global-require": [1],
10 | "import/prefer-default-export": [0],
11 | "react/jsx-no-bind": [0],
12 | "react/prop-types": [0],
13 | "react/prefer-stateless-function": [0],
14 | "no-else-return": [0],
15 | "no-restricted-syntax": [0],
16 | "import/no-extraneous-dependencies": [0],
17 | "no-use-before-define": [0],
18 | "jsx-a11y/no-static-element-interactions": [0],
19 | "no-nested-ternary": [0],
20 | "arrow-body-style": [0],
21 | "import/extensions": [0],
22 | "no-bitwise": [0],
23 | "no-cond-assign": [0],
24 | "import/no-unresolved": [0],
25 | "require-yield": [1]
26 | },
27 | "parserOptions": {
28 | "ecmaFeatures": {
29 | "experimentalObjectRestSpread": true
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | /node_modules
4 | /coverage
5 | /dist
6 | /logs
7 |
8 | .DS_Store
9 | .env
10 | .idea
11 | .vscode
12 | *.log
13 |
14 | npm-debug.log*
15 | yarn-debug.log*
16 | yarn-error.log*
17 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React-Koa-Mongodb技术栈
2 |
3 | ## 安装依赖
4 |
5 | 如果本机没有安装过yarn,请先执行`npm install -g yarn`;
6 |
7 | ```
8 | yarn install
9 | ```
10 |
11 | **友情提示**
12 | 1. 建议设置包镜像源为国内的:
13 |
14 | ```
15 | $ npm set registry https://registry.npm.taobao.org
16 | $ npm set disturl https://npm.taobao.org/dist
17 | $ npm set sass_binary_site http://cdn.npm.taobao.org/dist/node-sass
18 | $ yarn config set registry https://registry.npm.taobao.org;
19 | ```
20 |
21 | 2. 如果一直卡主无法成功安装完成,多半是因为`node-sass`资源被墙的问题,如:
22 |
23 | ```
24 | ...
25 | [-/5] ⠠ waiting...
26 | [-/5] ⠠ waiting...
27 | [-/5] ⠠ waiting...
28 | [-/5] ⠠ waiting...
29 | [5/5] ⠠ node-sass: g++ '-DNODE_GYP_MODULE_NAME=libsass' '-DUSING_UV_SHARED=1' '-DUSING_V8_SHARED=1' '-DV8_DEPRECATION_WARNINGS=1' '-D_
30 | ```
31 |
32 | 建议先取消安装,然后`cd node_modules/`,然后`rm -rf node-sass`,接着回到上级目录`cd ../`,执行:
33 |
34 | ```
35 | yarn add node-sass sass-loader css-loader style-loader postcss-loader
36 | ```
37 |
38 | ## 配置Nginx
39 |
40 | ```
41 | server {
42 | listen 80;
43 | server_name cims.thinktxt.com;
44 |
45 | location / {
46 | proxy_pass http://127.0.0.1:8000;
47 | }
48 | }
49 |
50 | server {
51 | listen 80;
52 | server_name static.cims.thinktxt.com;
53 |
54 | location / {
55 | proxy_pass http://127.0.0.1:8088;
56 | }
57 |
58 | }
59 | ```
60 |
61 | ## 绑定Host
62 |
63 | ```
64 | 127.0.0.1 cims.thinktxt.com
65 | 127.0.0.1 static.cims.thinktxt.com
66 | ```
67 |
68 | ## 本地开发构建
69 |
70 | 本地开发环境用`gulp server`命令。`gulp server`会启动一个基于内存的WEB服务器,端口为8088,同时监听(watch)本地文件的变化,然后自动编译。
71 |
72 | ```
73 | gulp server
74 | ```
75 |
76 | ## 启动Node服务
77 |
78 | ```
79 | node server/index.js
80 | ```
81 |
82 | 启动服务后在浏览器输入`http://cims.thinktxt.com`即可预览。
83 |
84 |
85 | ## 发布生产
86 |
87 | 正式环境的构建主要是增加了混淆、压缩的插件。
88 |
89 | ```
90 | gulp release
91 | ```
92 |
93 | 发布后可在`dist`目录查看所有静态资源
94 |
95 |
96 | ## 拓展服务配置
97 |
98 | ### 本地数据库(MongoDB)
99 |
100 | #### 安装
101 |
102 | * 打开终端,执行`brew install mongodb`
103 |
104 | * 在根目录 / 下创建 `data/db` 目录,用于放置`mongodb`数据,并且给该目录设置用户(txBoy)权限
105 |
106 | ```bash
107 | sudo mkdir -p /data/db
108 | sudo chown txBoy /data/db
109 | ```
110 |
111 | #### 启动
112 |
113 | ```
114 | mongod
115 | ```
116 |
117 | 或者指定`数据库路径`和`端口`启动:
118 |
119 | ```
120 | mongod --dbpath=/data/db --port=27017
121 | ```
122 |
123 | ### MongoDB客户端
124 |
125 | 推荐MongoDB可视化操作客户端[Robomongo](https://robomongo.org/download)
126 |
--------------------------------------------------------------------------------
/build/webpack.base.conf.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const { resolve } = require('path');
3 |
4 | const HtmlWebpackPlugin = require('html-webpack-plugin');
5 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
6 | const CommonsChunkPlugin = webpack.optimize.CommonsChunkPlugin;
7 |
8 | process.noDeprecation = true;
9 |
10 | module.exports = {
11 | //文件入口配置
12 | /*entry: {
13 | index: [
14 | 'react-hot-loader/patch',
15 | 'webpack-dev-server/client?http://127.0.0.1:9000',
16 | 'webpack/hot/only-dev-server',
17 | './src/entry/index.js'
18 | ]
19 | },*/
20 | //文件输出配置
21 | output: {
22 | path: resolve(__dirname, '../dist'), //打包输出目录
23 | },
24 | // 声明CDN加载的库,不会通过webpack打包
25 | externals: {
26 | "react": 'React',
27 | "react-dom": "ReactDOM",
28 | "react-router": "ReactRouter",
29 | // 'history': "History",
30 | // 'redux': 'Redux',
31 | // 'react-redux': 'ReactRedux',
32 | // 'lodash': '_'
33 | },
34 | //加载器配置
35 | module: {
36 | rules: [{
37 | test: /\.js?$/,
38 | exclude: /(node_modules|bower_components)/,
39 | loader: "babel-loader",
40 | options: {
41 | cacheDirectory: true
42 | }
43 | }, {
44 | test: /\.(png|jpe?g|gif)$/i,
45 | loader: 'url-loader',
46 | options: {
47 | limit: 8192,
48 | name:'images/[name].[hash:8].[ext]'
49 | }
50 | }, {
51 | test: /\.(woff|woff2|ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/i,
52 | use: [
53 | {
54 | loader: 'file-loader',
55 | options: {
56 | name:'images/[name]-[hash:6].[ext]'
57 | }
58 | }]
59 | }]
60 | },
61 | //插件项
62 | plugins: [
63 | /*new webpack.ProvidePlugin({
64 | 'Promise': 'bluebird',
65 | // 'React': 'react'
66 | }),*/
67 | // new webpack.optimize.ModuleConcatenationPlugin(), // webpack 3 新增的作用域提升插件
68 | new CommonsChunkPlugin({
69 | names:['manifest', 'vendor'].reverse(),
70 | minChunks: 2,
71 | }),
72 | new HtmlWebpackPlugin({
73 | alwaysWriteToDisk: true,
74 | filename: 'index.html',
75 | template: './src/views/index.html', //html模板路径
76 | chunks: ['manifest', 'vendor', 'index'] // manifest: 可以理解为模块清单,载货单
77 | })
78 | ],
79 | resolve: {
80 | extensions: ['.js', '.jsx', '.scss', '.css'],
81 | alias: {
82 | libs: resolve('./src/libs'),
83 | utils: resolve('./src/utils'),
84 | scss: resolve('./src/scss'),
85 | css: resolve('./src/css'),
86 | img: resolve('./src/images'),
87 | api: resolve('./src/api'),
88 | cpn: resolve('./src/components'),
89 | routes: resolve('./src/routes'),
90 | data: resolve('./src/data')
91 | },
92 | modules: ['node_modules']
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/build/webpack.dev.conf.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const merge = require('webpack-merge');
3 | const path = require("path");
4 |
5 | const HtmlWebpackHarddiskPlugin = require('html-webpack-harddisk-plugin');
6 | const InlineChunkWebpackPlugin = require('html-webpack-inline-chunk-plugin');
7 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
8 |
9 | const AggressiveMergingPlugin = webpack.optimize.AggressiveMergingPlugin;
10 |
11 | const webpackBaseConfig = require('./webpack.base.conf.js');
12 |
13 | var buildInfo = {
14 | assets: true, // 增加资源信息
15 | assetsSort: "field", // 对资源按指定的项进行排序
16 | cached: true, // 增加缓存了的(但没构建)模块的信息
17 | children: true, // 增加子级的信息
18 | chunks: false, // 增加包信息(设置为 `false` 能允许较少的冗长输出)
19 | chunkModules: false, // 将内置模块信息增加到包信息
20 | chunkOrigins: false, // 增加包 和 包合并 的来源信息
21 | chunksSort: "field", // 对包按指定的项进行排序
22 | context: "../src/", // 用于缩短请求的上下文目录
23 | colors: true, // 等同于`webpack --colors`
24 | errors: true, // 增加错误信息
25 | errorDetails: true, // 增加错误的详细信息(就像解析日志一样)
26 | hash: true, // 增加编译的哈希值
27 | modules: false, // 增加内置的模块信息
28 | modulesSort: "field", // 对模块按指定的项进行排序
29 | publicPath: true, // 增加 publicPath 的信息
30 | reasons: true, // 增加模块被引入的原因
31 | source: true, // 增加模块的源码
32 | timings: true, // 增加时间信
33 | version: false, // 增加 webpack 版本信息
34 | warnings: true // 增加提示
35 | }
36 |
37 | module.exports = merge(webpackBaseConfig, {
38 | entry: {
39 | index: [
40 | 'react-hot-loader/patch',
41 | 'webpack-dev-server/client?http://127.0.0.1:9000',
42 | 'webpack/hot/only-dev-server',
43 | './src/entry/index.dev.js'
44 | ],
45 | vendor: ['whatwg-fetch', 'es6-promise', 'fetch-detector']
46 | },
47 | output: {
48 | publicPath: '//static.cims.thinktxt.com/', //webpack-dev-server访问的路径
49 | filename: 'js/[name].[hash:8].js',
50 | chunkFilename: 'js/[id].chunk.[hash:8].js'
51 | },
52 | devtool: '#eval-source-map',
53 | devServer: {
54 | hot: true,
55 | contentBase: path.join(__dirname, "dist"),
56 | compress: true,
57 | port: 8088,
58 | stats: buildInfo
59 | },
60 | module: {
61 | rules: [{
62 | test: /\.(css|scss|sass)$/,
63 | use: [
64 | { loader: "style-loader" },
65 | { loader: "css-loader" },
66 | { loader: "postcss-loader" },
67 | { loader: "sass-loader" }
68 | ]
69 | /*use: ExtractTextPlugin.extract({
70 | fallback: 'style-loader',
71 | use: [
72 | {
73 | loader: 'css-loader'
74 | }, {
75 | loader: 'postcss-loader'
76 | }, {
77 | loader: 'sass-loader'
78 | }]
79 | })*/
80 | }]
81 | },
82 | //插件项
83 | plugins: [
84 | new webpack.HotModuleReplacementPlugin(), // enable HMR globally
85 | new webpack.NamedModulesPlugin(), // prints more readable module names in the browser console on HMR updates
86 |
87 | new webpack.HashedModuleIdsPlugin(), //稳定chunkhash
88 | new AggressiveMergingPlugin(), //Merge chunks
89 | new HtmlWebpackHarddiskPlugin(),
90 | new InlineChunkWebpackPlugin({
91 | inlineChunks: ['manifest']
92 | }),
93 | /*new ExtractTextPlugin({
94 | filename: 'css/[name].[hash:8].css'
95 | }),*/
96 | new webpack.LoaderOptionsPlugin({
97 | // test: /\.xxx$/, // may apply this only for some modules
98 | options: {
99 | hot: true,
100 | proxy: {
101 | "/api": "http://172.3.1.33:8086"
102 | }
103 | }
104 | })
105 | ]
106 | })
107 |
--------------------------------------------------------------------------------
/build/webpack.prod.conf.js:
--------------------------------------------------------------------------------
1 | const webpack = require('webpack');
2 | const merge = require('webpack-merge');
3 | const path = require("path");
4 |
5 | const autoprefixer = require('autoprefixer');
6 | const ExtractTextPlugin = require('extract-text-webpack-plugin');
7 |
8 | // const CleanWebpackPlugin = require('clean-webpack-plugin');
9 |
10 | const InlineChunkWebpackPlugin = require('html-webpack-inline-chunk-plugin');
11 | const AggressiveMergingPlugin = webpack.optimize.AggressiveMergingPlugin;
12 |
13 | const webpackBaseConfig = require('./webpack.base.conf.js');
14 |
15 | module.exports = merge(webpackBaseConfig, {
16 | //文件入口配置
17 | entry: {
18 | index: './src/entry/index.js',
19 | vendor: ['whatwg-fetch', 'es6-promise', 'fetch-detector']
20 | },
21 | //文件输出配置
22 | output: {
23 | publicPath: '/',
24 | filename: 'js/[name].[chunkhash:8].js',
25 | chunkFilename: 'js/[id].chunk.[chunkhash:8].js'
26 | },
27 | module: {
28 | rules: [{
29 | test: /\.(css|scss|sass)$/,
30 | use: [
31 | { loader: "style-loader" },
32 | {
33 | loader: "css-loader",
34 | options: {
35 | minimize: true
36 | }
37 | },
38 | { loader: "postcss-loader" },
39 | { loader: "sass-loader" }
40 | ]
41 | }]
42 | },
43 | devtool: false,
44 | //插件项
45 | plugins: [
46 | new webpack.HashedModuleIdsPlugin(), //稳定chunkhash
47 | new AggressiveMergingPlugin(), //Merge chunks
48 | new InlineChunkWebpackPlugin({
49 | inlineChunks: ['manifest']
50 | }),
51 | /*new ExtractTextPlugin({
52 | filename: 'css/[name].[contenthash:8].css'
53 | }),*/
54 | new webpack.LoaderOptionsPlugin({
55 | options: {
56 | postcss: function() {
57 | return [autoprefixer];
58 | }
59 | }
60 | }),
61 | /*new CleanWebpackPlugin(['dist'], {
62 | root: path.resolve(__dirname, '../'),
63 | verbose: true,
64 | dry: false,
65 | // exclude: ['shared.js']
66 | }),*/
67 | new webpack.optimize.UglifyJsPlugin({
68 | compress: {
69 | warnings: false,
70 | drop_debugger: true,
71 | drop_console: true
72 | }
73 | }),
74 | new webpack.DefinePlugin({
75 | "process.env": {
76 | NODE_ENV: JSON.stringify("production")
77 | }
78 | })
79 | ]
80 | })
81 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | const gulp = require('gulp');
2 | const gutil = require('gulp-util');
3 |
4 | const webpack = require('webpack');
5 | const webpackDevServer = require('webpack-dev-server');
6 |
7 | var devConfig = require('./build/webpack.dev.conf.js');
8 | var prodConfig = require('./build/webpack.prod.conf.js');
9 |
10 | var port = devConfig.devServer.port;
11 |
12 | var buildInfo = {
13 | assets: true, // 增加资源信息
14 | assetsSort: "field", // 对资源按指定的项进行排序
15 | cached: false, // 增加缓存了的(但没构建)模块的信息
16 | children: false, // 增加子级的信息
17 | chunks: false, // 增加包信息(设置为 `false` 能允许较少的冗长输出)
18 | chunkModules: false, // 将内置模块信息增加到包信息
19 | chunkOrigins: false, // 增加包 和 包合并 的来源信息
20 | chunksSort: "field", // 对包按指定的项进行排序
21 | context: "../src/", // 用于缩短请求的上下文目录
22 | colors: true, // 等同于`webpack --colors`
23 | errors: true, // 增加错误信息
24 | errorDetails: true, // 增加错误的详细信息(就像解析日志一样)
25 | hash: true, // 增加编译的哈希值
26 | modules: false, // 增加内置的模块信息
27 | modulesSort: "field", // 对模块按指定的项进行排序
28 | publicPath: true, // 增加 publicPath 的信息
29 | reasons: true, // 增加模块被引入的原因
30 | source: true, // 增加模块的源码
31 | timings: true, // 增加时间信
32 | version: false, // 增加 webpack 版本信息
33 | warnings: true // 增加提示
34 | }
35 |
36 | // 开发环境服务
37 | gulp.task('server', function(callback) {
38 | var compiler = webpack(devConfig);
39 | var serverConfig = {
40 | hot: true,
41 | contentBase: 'dist',
42 | stats: buildInfo
43 | }
44 | new webpackDevServer(compiler, serverConfig).listen(port, "127.0.0.1", function(err) {
45 | if (err) throw new gutil.PluginError("webpack-dev-server", err);
46 | // Server listening
47 | gutil.log("[webpack-dev-server]",
48 | "http://127.0.0.1:" + port);
49 | });
50 | });
51 |
52 | // 打包发布
53 | gulp.task('release', function(callback) {
54 | return webpack(prodConfig, function(err, stats) {
55 | if (err) throw new gutil.PluginError("webpack", err);
56 | gutil.log("[webpack]", stats.toString(buildInfo));
57 | callback();
58 | });
59 | });
60 |
61 | gulp.task('default', ['server'], function() {
62 | console.log( 'default task...' );
63 | });
64 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ant-demo",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "antd": "^2.8.2",
7 | "autoprefixer": "^6.7.7",
8 | "axios": "^0.16.1",
9 | "babel-core": "^6.24.0",
10 | "babel-loader": "^6.4.1",
11 | "babel-plugin-import": "^1.1.1",
12 | "babel-plugin-transform-es2015-modules-commonjs": "^6.24.0",
13 | "babel-plugin-transform-runtime": "^6.23.0",
14 | "babel-polyfill": "^6.23.0",
15 | "babel-preset-es2015": "^6.24.0",
16 | "babel-preset-react": "^6.23.0",
17 | "babel-preset-stage-0": "^6.22.0",
18 | "babel-preset-stage-3": "^6.22.0",
19 | "babel-register": "^6.24.0",
20 | "babel-runtime": "^6.23.0",
21 | "bcryptjs": "^2.4.3",
22 | "bluebird": "^3.5.0",
23 | "css-loader": "^0.28.0",
24 | "es6-promise": "^4.1.0",
25 | "extract-text-webpack-plugin": "^2.1.0",
26 | "fetch-detector": "^1.0.1",
27 | "gulp": "^3.9.1",
28 | "gulp-util": "^3.0.8",
29 | "html-webpack-harddisk-plugin": "^0.1.0",
30 | "html-webpack-inline-chunk-plugin": "^1.1.1",
31 | "html-webpack-plugin": "^2.28.0",
32 | "image-webpack-loader": "^3.2.0",
33 | "jsonwebtoken": "^7.3.0",
34 | "koa": "^2.2.0",
35 | "koa-body": "^1.6.0",
36 | "koa-router": "^7.1.1",
37 | "koa-session": "4.0.1",
38 | "koa-views": "^6.0.1",
39 | "lazy-route": "^1.0.7",
40 | "log4js": "^1.1.1",
41 | "mongoose": "^4.9.5",
42 | "node-sass": "^4.5.2",
43 | "postcss-load-config": "^1.2.0",
44 | "postcss-loader": "^1.3.3",
45 | "react": "^15.4.2",
46 | "react-dom": "^15.4.2",
47 | "react-hot-loader": "next",
48 | "react-router": "^4.0.0",
49 | "react-router-config": "^1.0.0-beta.1",
50 | "react-router-dom": "^4.0.0",
51 | "sass-loader": "^6.0.3",
52 | "style-loader": "^0.16.1",
53 | "svg-sprite-loader": "^0.3.0",
54 | "svg-url-loader": "^2.0.2",
55 | "webpack": "^2.2.1",
56 | "webpack-dev-server": "^2.4.2",
57 | "webpack-merge": "^4.1.0",
58 | "whatwg-fetch": "^2.0.3"
59 | },
60 | "devDependencies": {
61 | "react-scripts": "0.9.5"
62 | },
63 | "scripts": {
64 | "start": "react-scripts start",
65 | "build": "react-scripts build",
66 | "test": "react-scripts test --env=jsdom",
67 | "eject": "react-scripts eject"
68 | },
69 | "theme": {
70 | "primary-color": "#1DA57A"
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: [
3 | require('autoprefixer')
4 | ]
5 | };
6 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joyotang/React-Koa-Mongodb/aff8ddac9c2065e26619fc5d700af3a7ae61cc52/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
16 | React App
17 |
18 |
19 |
20 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/server/app.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @overview: Koa App.js
3 | * @author: txBoy
4 | * @created 2017-03-31
5 | */
6 | "use strict";
7 | import Koa from 'koa';
8 | import Router from 'koa-router';
9 | import koaBody from 'koa-body';
10 | import views from 'koa-views';
11 | import path from 'path';
12 | import queryString from 'querystring';
13 | import {config, logConfig} from './config';
14 | import sender from './utils/sender';
15 | import appRoutes from './routers';
16 | import mongo from './config/mongoose';
17 | import checkOrigin from './middleware/checkOrigin';
18 | import { refreshToken } from './middleware/authControl';
19 |
20 | // logger
21 | import loghelper from './utils/loghelper';
22 | const logger = loghelper('server');
23 |
24 | const app = new Koa();
25 | const router = new Router();
26 | const port = process.env.PORT || 8000;
27 |
28 | app.use(views(path.resolve(__dirname, '../dist'), {
29 | extension: 'html'
30 | }))
31 |
32 | app.use(koaBody());
33 |
34 | var db = mongo();
35 | db.connection.on("error", function (error) {
36 | console.log("------数据库连接失败:" + error);
37 | });
38 | db.connection.on("open", function () {
39 | console.log("------数据库连接成功!------");
40 | });
41 |
42 | app.use(async (ctx, next) => {
43 | var path = ctx.path;
44 | if (path.startsWith('/api')) {
45 | console.log('-----api-----')
46 | await refreshToken(ctx, next)
47 | } else {
48 | console.log('-----index-----')
49 | await ctx.render('index', {
50 | data: 'txBoy'
51 | });
52 | }
53 | })
54 |
55 | appRoutes(app, router); // app routes config.
56 |
57 | app.listen(port, () => {
58 | console.log('Server started on port ' + port);
59 | })
60 |
--------------------------------------------------------------------------------
/server/config/index.js:
--------------------------------------------------------------------------------
1 | import { resolve } from 'path';
2 |
3 | const config = {
4 | host: '127.0.0.1',
5 | dbHost: '127.0.0.1',
6 | dbPort: '27017',
7 | dbName: 'cims',
8 | mongodb: 'mongodb://127.0.0.1:27017/cims',
9 | cookieSecret: 'cimsCookie',
10 | jwt: {
11 | secret: 'vimplus'
12 | }
13 | }
14 |
15 | // Config for log4js.
16 | const logConfig = {
17 | appenders: [{
18 | "type": "dateFile",
19 | "filename": resolve(__dirname, "../../logs/server.log"),
20 | "pattern": "-yyyy-MM-dd.log",
21 | "category": "server"
22 | }, {
23 | "type": "dateFile",
24 | "filename": resolve(__dirname, "../../logs/mongodb.log"),
25 | "pattern": "-yyyy-MM-dd.log",
26 | "category": "mongodb"
27 | }]
28 | }
29 |
30 | export {config, logConfig}
31 |
--------------------------------------------------------------------------------
/server/config/mongoose.js:
--------------------------------------------------------------------------------
1 | import mongoose from 'mongoose';
2 | import { config } from './index';
3 |
4 | export default function () {
5 | mongoose.Promise = global.Promise;
6 | let db = mongoose.connect(config.mongodb);
7 | return db;
8 | }
9 |
--------------------------------------------------------------------------------
/server/config/statusCode.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | // 0* 通用错误
4 | const common = {
5 | success: {
6 | code: 10000,
7 | msg: '请求成功!',
8 | status: 'success'
9 | },
10 | error: {
11 | code: 99999,
12 | msg: 'System Error!',
13 | status: 'error'
14 | },
15 | addFail: {
16 | code: 99999,
17 | msg: '新增失败!',
18 | status: 'error'
19 | },
20 | updateFail: {
21 | code: 99999,
22 | msg: '更新失败!',
23 | status: 'error'
24 | },
25 | deleteFail: {
26 | code: 99999,
27 | msg: '删除失败!',
28 | status: 'error'
29 | },
30 | none: {
31 | code: 90005,
32 | msg: '参数不能为空!',
33 | status: 'error'
34 | },
35 | noType: {
36 | code: 90005,
37 | msg: '类型不能为空!',
38 | status: 'error'
39 | },
40 | noneId: {
41 | code: 90006,
42 | msg: 'id不能为空!',
43 | status: 'error'
44 | },
45 | missParam: {
46 | code: 90007,
47 | msg: '请检查所需的参数!',
48 | status: 'error'
49 | },
50 | notLogin: {
51 | code: 88888,
52 | msg: '未登录或登录超时,请重新登录!',
53 | status: 'error'
54 | },
55 | invalidToken: {
56 | code: 88888,
57 | msg: 'Invalid Token!',
58 | status: 'error'
59 | },
60 | errorToken: {
61 | code: 88888,
62 | msg: 'Token verify failed!',
63 | status: 'error'
64 | },
65 | timeOut: {
66 | code: 50004,
67 | msg: '处理超时!',
68 | status: 'error'
69 | },
70 | notPermission: {
71 | code: 50005,
72 | msg: '您暂时无权进行此操作,请联系管理员授权!',
73 | status: 'error'
74 | }
75 | }
76 |
77 | // 1* 为用户报错信息
78 | const account = {
79 | notInfo: {
80 | code: 10001,
81 | msg: '用户名或密码不能为空!',
82 | status: 'error'
83 | },
84 | dupName: {
85 | code: 10002,
86 | msg: '用户名已存在!',
87 | status: 'error'
88 | },
89 | notUser: {
90 | code: 10003,
91 | msg: '用户不存在!',
92 | status: 'error'
93 | },
94 | pwdError: {
95 | code: 10004,
96 | msg: '用户名或密码错误!',
97 | status: 'error'
98 | },
99 | noAccess: {
100 | code: 10005,
101 | msg: '暂无访问权限!',
102 | status: 'error'
103 | },
104 | oldPwdErr: {
105 | code: 10006,
106 | msg: '原密码输入错误!'
107 | },
108 | notMatch: {
109 | code: 10007,
110 | msg: '用户名和密码不匹配,请核对!',
111 | },
112 | forbidden: {
113 | code: 10008,
114 | msg: '该用户已被限制登录,详情咨询管理员!',
115 | }
116 | }
117 |
118 | export {
119 | common,
120 | account
121 | }
122 |
--------------------------------------------------------------------------------
/server/config/winston.conf.js:
--------------------------------------------------------------------------------
1 | import winston from 'winston';
2 | import 'winston-daily-rotate-file';
3 |
4 | winston.loggers.add('server', {
5 | console: {
6 | level: 'info',
7 | colorize: true,
8 | label: 'Server-log'
9 | },
10 | file: {
11 | filename: 'logs/server.log'
12 | }
13 | });
14 |
15 | var transport = new winston.transports.DailyRotateFile({
16 | filename: 'logs/log',
17 | datePattern: 'yyyy-MM-dd.',
18 | prepend: true,
19 | level: process.env.ENV === 'development' ? 'debug' : 'info'
20 | });
21 |
22 | const logConfig = {
23 | transports: [
24 | transport
25 | ]
26 | }
27 |
28 | export {
29 | logConfig
30 | };
31 |
--------------------------------------------------------------------------------
/server/controllers/user.controller.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @overview: user controller - Provide user related controller.
3 | * @author: txBoy
4 | * @created 2017-04-17
5 | */
6 |
7 | import jwt from 'jsonwebtoken';
8 | import { md5 } from '../utils/util';
9 | import { config } from '../config';
10 | import { common } from '../config/statusCode';
11 | import { generateToken } from '../middleware/authControl';
12 | import userService from '../services/user.service';
13 |
14 | var user = {
15 | register: async function (ctx, next) {
16 | var userInfo = ctx.request.body;
17 | var data = await userService.register(userInfo);
18 | if (data) {
19 | ctx.body = Object.assign({data: data}, common.success);
20 | } else {
21 | ctx.body = Object.assign({data: null}, common.error);
22 | }
23 | },
24 | login: async function (ctx, next) {
25 | var userInfo = ctx.request.body;
26 | console.log('Req:', userInfo)
27 | var res = await userService.login(userInfo);
28 | console.log('loginData:', res);
29 |
30 | if (res && res.data) {
31 | var tokenInfo = {username: res.data.username};
32 | var token = generateToken(tokenInfo);
33 | ctx.cookies.set('token', token);
34 | ctx.body = res;
35 | } else {
36 | ctx.body = res;
37 | }
38 | },
39 | logout: async function (ctx, next) {
40 | ctx.cookies.set('token', '');
41 | ctx.body = Object.assign({data: true}, common.success);
42 | },
43 | getList: async function (ctx, next) {
44 | var params = ctx.query;
45 | params.filter = data && params.filter ? JSON.parse(params.filter) : {};
46 | var data = await userService.getUsers(params);
47 | if (data) {
48 | ctx.body = Object.assign({data: data}, common.success);
49 | } else {
50 | ctx.body = Object.assign({data: null}, common.error);
51 | }
52 | }
53 | }
54 |
55 | export default user;
56 |
--------------------------------------------------------------------------------
/server/controllers/webIssues.controller.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @overview: webIssues controller - Provide webIssues related controller.
3 | * @author: txBoy
4 | * @created 2017-07-04
5 | */
6 |
7 | import { common } from '../config/statusCode';
8 | import webIssuesService from '../services/webIssues.service';
9 | import checkOrigin from '../middleware/checkOrigin';
10 |
11 | var webIssues = {
12 | push: async function (ctx, next) {
13 | var data = ctx.request.body;
14 | // var data = ctx.query;
15 | console.log('------data--------', data)
16 | var doc = await webIssuesService.add(data);
17 | var origin = ctx.headers.origin;
18 | console.log('------origin:',origin);
19 |
20 | if (checkOrigin(origin)) {
21 | ctx.set('Access-Control-Allow-Origin', origin);
22 | ctx.set('Access-Control-Allow-Credentials', true);
23 | ctx.set('Access-Control-Allow-Headers', 'x-requested-with,content-type');
24 | }
25 |
26 | if (doc) {
27 | var res = Object.assign({data: null}, common.success);
28 | res.data = doc;
29 | ctx.response.body = res;
30 | } else {
31 | var res = Object.assign({data: null}, common.error);
32 | ctx.response.body = res;
33 | }
34 | }
35 | }
36 |
37 | export default webIssues;
38 |
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 |
2 | var register = require('babel-register');
3 |
4 | register({
5 | presets: ['es2015', 'stage-3']
6 | });
7 | require('babel-polyfill');
8 | require('./app.js');
9 | global.navigator = { userAgent: 'all' }
10 |
--------------------------------------------------------------------------------
/server/middleware/authControl.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @overview: Authentication - Verify user token.
3 | * @author: txBoy
4 | * @created 2017-04-17
5 | */
6 |
7 | import jwt from 'jsonwebtoken';
8 | import { config } from '../config';
9 | import { common } from '../config/statusCode';
10 |
11 | // logger
12 | import loghelper from '../utils/loghelper';
13 | const logger = loghelper('server');
14 |
15 | const expiredTime = 60 * 30; // 30min
16 | const sKeys = config.jwt.secret;
17 |
18 | function generateToken(data) {
19 | var token = jwt.sign(data, sKeys, {expiresIn: expiredTime});
20 | return token;
21 | }
22 |
23 | function handleTokenError(ctx, msg) {
24 | ctx.cookies.set('token', '');
25 | switch (msg) {
26 | case 'jwt expired':
27 | ctx.body = Object.assign({data: null}, common.notLogin);
28 | break;
29 | case 'invalid token':
30 | ctx.body = Object.assign({data: null}, common.invalidToken);
31 | break;
32 | case 'jwt malformed':
33 | ctx.body = Object.assign({data: null}, common.errorToken);
34 | break;
35 | default:
36 | ctx.body = Object.assign({data: null}, common.errorToken);
37 | }
38 | }
39 |
40 | async function authVerify(ctx, next) {
41 | const token = ctx.cookies.get('token');
42 | if (!token) {
43 | ctx.body = Object.assign({data: null}, common.notLogin);
44 | return;
45 | }
46 | try {
47 | var tokenContent = await jwt.verify(token, sKeys);
48 | console.log('----------verify tokenContent:', tokenContent);
49 | console.log("Authentication success!");
50 | await next();
51 |
52 | } catch(err) {
53 | logger.error('---------------Verify Error:', err);
54 | if (err && err.message) {
55 | handleTokenError(ctx, err.message);
56 | } else {
57 | ctx.body = Object.assign({data: null}, common.error);
58 | }
59 | }
60 | }
61 |
62 |
63 | async function refreshToken(ctx, next) {
64 | var token = ctx.cookies.get('token');
65 | if (token) {
66 | try {
67 | var tokenContent = await jwt.verify(token, sKeys);
68 | delete tokenContent.iat;
69 | delete tokenContent.exp;
70 |
71 | var newToken = jwt.sign(tokenContent, sKeys, {expiresIn: expiredTime});
72 | ctx.cookies.set('token', newToken); // 更新token, 延长过期时间
73 |
74 | await next();
75 | } catch (err) {
76 | logger.error('---------------RefreshToken Error:', err);
77 | if (err && err.message) {
78 | handleTokenError(ctx, err.message);
79 | } else {
80 | ctx.body = Object.assign({data: null}, common.error);
81 | }
82 | }
83 | } else {
84 | await next();
85 | }
86 | }
87 |
88 | export default authVerify;
89 |
90 | export {
91 | generateToken,
92 | authVerify,
93 | refreshToken
94 | };
95 |
--------------------------------------------------------------------------------
/server/middleware/checkOrigin.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @overview: Check allow origin.
3 | * @author: txBoy
4 | * @created 2017-07-05
5 | */
6 |
7 | function checkOrigin(origin) {
8 | var whiteList = [
9 | 'http://lms-dev.yylending.com',
10 | 'http://127.0.0.1:4000'
11 | ]
12 | if (whiteList.indexOf(origin) != -1) {
13 | return true;
14 | }
15 | return false;
16 | }
17 |
18 | export default checkOrigin;
19 |
--------------------------------------------------------------------------------
/server/models/commons/counters.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @overview: Auto increment id. - Provide Auto increment id for mongodb.
3 | * @author: txBoy
4 | * @created 2017-07-05
5 | */
6 |
7 | import mongoose from 'mongoose';
8 | const Schema = mongoose.Schema;
9 |
10 | var CounterSchema = Schema({
11 | tableName: {type: String, required: true},
12 | seq: { type: Number, default: 1 }
13 | });
14 | var Counter = mongoose.model('cims_counters', CounterSchema);
15 |
16 | async function getIncrementId(tableName) {
17 | try {
18 | var counter = await Counter.findOneAndUpdate({tableName: tableName}, {$inc: { seq: 1} });
19 | if (counter) {
20 | return counter && counter.seq;
21 | }
22 | } catch (err) {
23 | throw err;
24 | return null;
25 | }
26 | }
27 |
28 | export default getIncrementId;
29 |
--------------------------------------------------------------------------------
/server/models/user.model.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @overview: user model. - Provide user related data Schema.
3 | * @author: txBoy
4 | * @created 2017-04-17
5 | */
6 |
7 | import mongoose from 'mongoose';
8 | const Schema = mongoose.Schema;
9 |
10 | // 定义数据库中的集合名称(相当于 MySQL 数据库中的表名),如果有则保存到该集合,如果没有则创建名为 cims_users 的集合后保存数据。
11 | const COLLECTTION = 'cims_users';
12 |
13 | var CounterSchema = Schema({
14 | _id: {type: String, required: true},
15 | seq: { type: Number, default: 1 }
16 | });
17 | var Counter = mongoose.model('counters', CounterSchema);
18 |
19 | // 定义user的数据模型。
20 | var UserSchema = new Schema({
21 | uid: { type: Number },
22 | username: {
23 | type: String,
24 | unique: true,
25 | trim: true,
26 | required: true,
27 | index: true
28 | },
29 | name: String,
30 | nickname: String,
31 | email: {
32 | type: String,
33 | validate: {
34 | validator: function(email) {
35 | return /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/.test(email);
36 | },
37 | message: '{VALUE} is not a valid email!'
38 | }
39 | },
40 | password: {
41 | type: String,
42 | required: true
43 | },
44 | state: {
45 | type: String,
46 | enum: ["single", "inlove"]
47 | },
48 | gender: {
49 | type: Number,
50 | enum: [1, 2, 3],
51 | default: 3
52 | },
53 | phoneNo: String,
54 | accessToken: String,
55 | avatar: String,
56 | location: String,
57 | url: String,
58 | signature: String,
59 | createTime: {
60 | type: Number,
61 | default: Date.now()
62 | },
63 | updateTime: Number
64 | });
65 |
66 | UserSchema.set('autoIndex', true);
67 |
68 | // 根据Schema创建一个Model
69 | var UserModel = mongoose.model(COLLECTTION, UserSchema);
70 |
71 | export default UserModel;
72 |
--------------------------------------------------------------------------------
/server/models/webIssues.model.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @overview: Web-issues model. - Provide Web-issues related data Schema.
3 | * @author: txBoy
4 | * @created 2017-07-04
5 | */
6 |
7 | import mongoose from 'mongoose';
8 | const Schema = mongoose.Schema;
9 |
10 | const COLLECTTION = 'cims_web_issues';
11 |
12 | // 定义数据模型。
13 | var issuesSchema = new Schema({
14 | logId: Number,
15 | title: String,
16 | category: String,
17 | browserType: String,
18 | osType: String,
19 | environments: Object,
20 | userInfo: Object,
21 | stack: String,
22 | targetUrl: String,
23 | status: Number,
24 | crashCount: Number,
25 | createdTime: Number,
26 | updateTime: Number,
27 | lastCrashedTime: Number
28 | });
29 |
30 | // 根据Schema创建一个Model
31 | var issuesModel = mongoose.model(COLLECTTION, issuesSchema);
32 |
33 | export default issuesModel;
34 |
--------------------------------------------------------------------------------
/server/routers/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @overview koa-router config.
3 | * @author txBoy
4 | * @created 2017-03-21
5 | */
6 |
7 | import sender from '../utils/sender';
8 | import authVerify from '../middleware/authControl';
9 | import user from '../controllers/user.controller';
10 | import webIssues from '../controllers/webIssues.controller';
11 |
12 | function appRoutes(app, router) {
13 | app.use(router.routes()).use(router.allowedMethods());
14 |
15 | router.post('/api/user/register', user.register);
16 | router.post('/api/user/login', user.login);
17 | router.get('/api/user/getUsers', authVerify, user.getList);
18 | router.get('/api/user/logout', user.logout);
19 |
20 | router.get('/api/webIssues/push', webIssues.push);
21 | router.post('/api/webIssues/push', webIssues.push);
22 |
23 | }
24 |
25 | export default appRoutes;
26 |
--------------------------------------------------------------------------------
/server/services/user.service.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @overview: user service - Provide user related database operations services.
3 | * @author: txBoy
4 | * @created 2017-04-17
5 | */
6 |
7 | import mongoose from 'mongoose';
8 |
9 | import { md5 } from '../utils/util';
10 | import { config } from '../config';
11 | import { common, account } from '../config/statusCode';
12 | import UserModel from '../models/user.model';
13 | import getIncrementId from '../models/commons/counters';
14 |
15 | // logger
16 | import loghelper from '../utils/loghelper';
17 | const logger = loghelper('mongodb');
18 |
19 | const COLLECTTION = 'cims_users';
20 | const User = mongoose.model(COLLECTTION);
21 |
22 | var userService = {
23 | register: async function (info) {
24 | try {
25 | var uid = await getIncrementId(COLLECTTION);
26 | if (uid) {
27 | info.uid = uid;
28 | info.updateTime = Date.now();
29 | var doc = await User.create(info);
30 | if (doc) {
31 | return {
32 | username: doc.username,
33 | email: doc.email
34 | }
35 | }
36 | } else {
37 | return null;
38 | }
39 | } catch (err) {
40 | logger.error('---------------The register action DB-Error:', err);
41 | console.log('---------------The register action DB-Error:', err);
42 | return null;
43 | }
44 | },
45 | login: async function (info) {
46 | try {
47 | var doc = await User.findOne({username: info.username});
48 | if (doc) {
49 | var res = null;
50 | if (info.password === doc.password) {
51 | var data = {
52 | uid: doc.uid,
53 | username: doc.username,
54 | email: doc.email
55 | }
56 | res = Object.assign({data: data}, common.success);
57 | } else {
58 | res = Object.assign({data: null}, account.pwdError);
59 | }
60 | return res;
61 | } else {
62 | var res = Object.assign({data: null}, account.notUser);
63 | return res;
64 | }
65 | } catch (err) {
66 | logger.error('---------------The login action DB-Error:', err);
67 | console.log('---------------The login action DB-Error:', err);
68 | var res = Object.assign({data: null}, common.error);
69 | return res;
70 | }
71 | },
72 | getUsers: async function (params) {
73 | // 页码查询参数
74 | var page = Number(params && params.page) || 1;
75 | var size = Number(params && params.size) || 10;
76 | var total = 0;
77 | // 筛选条件组装
78 | var { searchValue } = params.filter;
79 | var filter = {};
80 | if (searchValue) { filter.$or = [{username: new RegExp(searchValue, 'i')}, {realName: new RegExp(searchValue, 'i')}] } // 根据[用户名]或[姓名]模糊查询
81 |
82 | try {
83 | total = await User.count(filter);
84 | var userList = await User.find(filter, null, {skip: (page - 1) * size, limit: size});
85 | if (userList) {
86 | var list = [];
87 | for (var i = 0; i < userList.length; i++) {
88 | var obj = {};
89 | var user = userList[i];
90 | obj.id = user._id;
91 | obj.uid = user.uid;
92 | obj.username = user.username;
93 | obj.email = user.email;
94 | obj.gender = user.gender;
95 | obj.createTime = user.createTime;
96 | list.push(obj);
97 | }
98 | return {
99 | list: list,
100 | page: page,
101 | size: size,
102 | total: total
103 | }
104 | }
105 | } catch (err) {
106 | logger.error('---------------The getUsers action DB-Error:', err);
107 | return null;
108 | }
109 | }
110 | }
111 |
112 | export default userService;
113 |
--------------------------------------------------------------------------------
/server/services/webIssues.service.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @overview: webIssues service - Provide webIssues related database operations services.
3 | * @author: txBoy
4 | * @created 2017-07-04
5 | */
6 |
7 | import mongoose from 'mongoose';
8 | import { config } from '../config';
9 | import WebIssuesModel from '../models/webIssues.model';
10 | import getIncrementId from '../models/commons/counters';
11 |
12 | // logger
13 | import loghelper from '../utils/loghelper';
14 | const logger = loghelper('mongodb');
15 |
16 | const COLLECTTION = 'cims_web_issues';
17 | const WebIssues = mongoose.model(COLLECTTION);
18 |
19 | var webIssuesService = {
20 | add: async function (info) {
21 | try {
22 | info.updateTime = Date.now();
23 | // console.log('-------info:', info)
24 | var doc = await WebIssues.findOne({title: info.title});
25 | if (doc) {
26 | info.lastCrashedTime = doc.updateTime;
27 | var result = await WebIssues.updateOne({title: info.title}, {$set: info});
28 | if (result && result.n) {
29 | var updatedDoc = await WebIssues.findOne({title: info.title});
30 | // console.log('-------updatedDoc:', updatedDoc)
31 | return updatedDoc;
32 | } else {
33 | return null;
34 | }
35 | } else {
36 | info.createdTime = Date.now();
37 | var logId = await getIncrementId(COLLECTTION);
38 | if (logId) {
39 | info.logId = logId;
40 | doc = await WebIssues.create(info);
41 | console.log('-------doc:', doc)
42 | return doc;
43 | }
44 | }
45 | } catch (err) {
46 | logger.error('---------------The webIssues add action DB-Error:', err);
47 | console.log('DB-Error:', err)
48 | return null;
49 | }
50 | }
51 | }
52 |
53 | export default webIssuesService;
54 |
--------------------------------------------------------------------------------
/server/utils/loghelper.js:
--------------------------------------------------------------------------------
1 | /****************************************************************
2 | * Purpose: handle log4js.
3 | *
4 | * @author: Tower.
5 | * @time: 2017-05-23.
6 | */
7 |
8 | import { logConfig } from '../config';
9 | import log4js from 'log4js';
10 | log4js.configure(logConfig);
11 |
12 | let loggersMap = {};
13 | const CATEGORY = 'category';
14 | const APPENDERS = 'appenders';
15 |
16 | function isEmptyObject(o) {
17 | for (let k in o) return false;
18 | return true;
19 | };
20 |
21 | if (isEmptyObject(loggersMap)) {
22 | logConfig && logConfig[APPENDERS].forEach(function(appender) {
23 | if (appender.hasOwnProperty(CATEGORY)) {
24 | loggersMap[appender[CATEGORY]] = log4js.getLogger(appender[CATEGORY]);
25 | }
26 | });
27 | }
28 |
29 | /*
30 | Purpose: logger public module
31 | @param {Object} category
32 | @return {Object} logger
33 | @example
34 | const logger = require('xx/utils/loghelper')('yourCategory');
35 | logger.error('log error');
36 | logger.info('log info');
37 | */
38 | export default function(category) {
39 | if (!category || category.trim() == '') {
40 | let stack = new Error().stack;
41 | let callLocation = stack.split('at')[2].split('/')[stack.split('at')[2].split('/').length-1].replace(/\)/g, '');
42 | console.error(`Missing loghelper init params: category, at: ${callLocation}`);
43 | loggersMap['server'] && loggersMap['server'].error(new Error('Missing loghelper init params: category'));
44 | }
45 | if (!loggersMap.hasOwnProperty(category)) {
46 | console.error(`Can't find logger instance: ${category}`);
47 | loggersMap['server'] && loggersMap['server'].error(new Error(`Can't find logger instance: ${category}`));
48 | }
49 |
50 | return loggersMap[category];
51 | };
52 |
--------------------------------------------------------------------------------
/server/utils/sender.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @overview: sender
3 | * @author: txBoy
4 | * @created 2017-04-07
5 | */
6 |
7 | import axios from 'axios';
8 | // import xRequest from './xRequest';
9 |
10 |
11 | var sender = {};
12 |
13 |
14 | sender.get = function (url, options) {
15 | options.method = 'GET';
16 | options.params = options.data || null;
17 | return this.request(url, options);
18 | }
19 |
20 |
21 | sender.post = function (url, options) {
22 | options.method = 'POST';
23 | return this.request(url, options);
24 | }
25 |
26 | sender.request = function (url, options) {
27 | var options = Object.assign({}, options);
28 | options.url = url;
29 | return xRequest(options);
30 | }
31 |
32 | function xRequest(options) {
33 | return axios(options);
34 | }
35 |
36 | export default sender;
37 |
--------------------------------------------------------------------------------
/server/utils/util.js:
--------------------------------------------------------------------------------
1 |
2 | import crypto from 'crypto';
3 | import fs from 'fs';
4 | import path from 'path';
5 |
6 | /**
7 | * md5正常字符串加密
8 | */
9 | function md5(str) {
10 | var ret = crypto.createHash('md5').update(str.toString()).digest('hex');
11 | return ret;
12 | }
13 |
14 | /**
15 | * md5中文字符串加密
16 | * @desc 解决中文加密不同的问题
17 | */
18 | function md5Pay(str) {
19 | str = (new Buffer(str)).toString("binary");
20 | var ret = crypto.createHash('md5').update(str).digest("hex");
21 | return ret;
22 | }
23 |
24 | /**
25 | * aes 加密
26 | */
27 | function encryptByAES(key, iv, data) {
28 | var cipher = crypto.createCipheriv('aes-128-cbc', key, iv);
29 | var crypted = cipher.update(data, 'utf8', 'binary');
30 | crypted += cipher.final('binary');
31 | crypted = new Buffer(crypted, 'binary').toString('base64');
32 | return crypted;
33 | }
34 |
35 | /**
36 | * aes 解密
37 | */
38 | function decryptByAES(key, iv, crypted) {
39 | crypted = new Buffer(crypted, 'base64').toString('binary');
40 | var decipher = crypto.createDecipheriv('aes-128-cbc', key, iv);
41 | var decoded = decipher.update(crypted, 'binary', 'utf8');
42 | decoded += decipher.final('utf8');
43 | return decoded;
44 | }
45 |
46 | /**
47 | * 格式化时间戳 - 时间毫秒数转化为固定格式的时间字符串
48 | * @param timestamp
49 | * @param dateFormate
50 | * @description 其中dateFormate是有YYYY MM DD hh mm ss组成,可以是YYYY-MM-DD 也可以是YYYY-MM-DD hh:mm:ss
51 | * 如果不传的话,默认为: YYYY-MM-DD hh:mm:ss
52 | * @example formatTimestamp(1464314911403);
53 | * formatTimestamp(1464314911403, 'yyyyMMddhhmmss');
54 | * formatTimestamp(1464314911403,'YYYY-MM-DD');
55 | */
56 | function formatTimestamp(timestamp, dateFormate) {
57 | var datemills = parseInt(timestamp);
58 | if (isNaN(datemills)) {
59 | console.error("Wrong timestamp, must be a number!");
60 | return;
61 | }
62 | if (dateFormate == undefined) {
63 | dateFormate = "YYYY-MM-DD hh:mm:ss";
64 | }
65 | var date = new Date(datemills);
66 | var year = "" + date.getFullYear();
67 | var month = (date.getMonth() > 8 ? (date.getMonth() + 1) : "0" + (date.getMonth() + 1));
68 | var day = date.getDate() > 9 ? date.getDate() : "0" + date.getDate();
69 | var hour = date.getHours() > 9 ? date.getHours() : "0" + date.getHours();
70 | var minute = date.getMinutes() > 9 ? date.getMinutes() : "0" + date.getMinutes();
71 | var second = date.getSeconds() > 9 ? date.getSeconds() : "0" + date.getSeconds();
72 | var datestr = dateFormate.replace(/yyyy/gi, year).replace('MM', month).replace(/dd/gi, day)
73 | .replace("hh", hour).replace("mm", minute).replace("ss", second);
74 | return datestr;
75 | }
76 |
77 | /**
78 | * 对象升序排序
79 | * @desc 对象按参数名称(key)进行升序排序
80 | * @param {Object}
81 | * @return {Object}
82 | */
83 | function sortObj(obj) {
84 | var keys = Object.keys(obj);
85 | keys.sort();
86 | var res = {};
87 | for (var i = 0; i < keys.length; i++) {
88 | var _key = keys[i];
89 | res[_key] = obj[_key];
90 | }
91 | return res;
92 | }
93 |
94 | /**
95 | * 分转元
96 | */
97 | function pennyToYuan(penny) {
98 | return (parseInt(penny) / 100).toFixed(2);
99 | };
100 |
101 | /**
102 | * 金额格式化
103 | * @desc 将数值四舍五入(保留2位小数)后格式化成金额形式
104 | * @param sum 数值(Number或者String)
105 | * @return {String} 金额格式的字符串,如'1,234,567.45'
106 | */
107 | function formatCurrency(sum) {
108 | if (!sum) return 0;
109 | var num = pennyToYuan(sum);
110 | num = num.toString().replace(/\$|\,/g, '');
111 | if(isNaN(num)) {
112 | num = "0";
113 | }
114 | var sign = (num == (num = Math.abs(num)) );
115 | num = Math.floor(num * 100 + 0.50000000001);
116 | var cents = num % 100;
117 | num = Math.floor(num / 100).toString();
118 |
119 | cents = cents < 10 ? '0' + cents : cents;
120 |
121 | for (var i = 0; i < Math.floor( (num.length-(1+i)) / 3 ); i++) {
122 | num = num.substring(0, num.length-(4*i+3)) + ',' + num.substring(num.length - (4 * i + 3) );
123 | }
124 | return ( ( (sign) ? '' : '-' ) + num + '.' + cents);
125 | }
126 |
127 | /**
128 | * 金额转大写
129 | * @desc 将数字金额转为中文大写
130 | * @param {Number}
131 | * @return {String}
132 | * @example digitUppercase(7682.01); // 柒仟陆佰捌拾贰元壹分
133 | * digitUppercase(7682); //柒仟陆佰捌拾贰元整
134 | * digitUppercase(951434677682.00); //玖仟伍佰壹拾肆亿叁仟肆佰陆拾柒万柒仟陆佰捌拾贰元整
135 | */
136 | function digitUppercase(n) {
137 | var fraction = ['角', '分'];
138 | var digit = [
139 | '零', '壹', '贰', '叁', '肆',
140 | '伍', '陆', '柒', '捌', '玖'
141 | ];
142 | var unit = [
143 | ['元', '万', '亿'],
144 | ['', '拾', '佰', '仟']
145 | ];
146 | var head = n < 0 ? '欠' : '';
147 | n = Math.abs(n);
148 | var s = '';
149 | for (var i = 0; i < fraction.length; i++) {
150 | s += (digit[Math.floor(n * 10 * Math.pow(10, i)) % 10] + fraction[i]).replace(/零./, '');
151 | }
152 | s = s || '整';
153 | n = Math.floor(n);
154 | for (var i = 0; i < unit[0].length && n > 0; i++) {
155 | var p = '';
156 | for (var j = 0; j < unit[1].length && n > 0; j++) {
157 | p = digit[n % 10] + unit[1][j] + p;
158 | n = Math.floor(n / 10);
159 | }
160 | s = p.replace(/(零.)*零$/, '').replace(/^$/, '零') + unit[0][i] + s;
161 | }
162 | return head + s.replace(/(零.)*零元/, '元')
163 | .replace(/(零.)+/g, '零')
164 | .replace(/^整$/, '零元整');
165 | };
166 |
167 | /**
168 | * 处理模板
169 | * @desc 将模板文件替换为实际数据
170 | * @param temp字符串
171 | * @param data 实际数据
172 | * @return 返回已替换为真实数据的字符串
173 | * @example handleTemplate('张三的手机号是${phoneNo}', {phoneNo: '17712345678'}); // 张三的手机号是17712345678
174 | */
175 | function handleTemplate(temp, data) {
176 | for (var key in data) {
177 | var reg = new RegExp("\\$\\{" + key + "\\}", "g");
178 | temp = temp.replace(reg, data[key]);
179 | }
180 | return temp;
181 | }
182 |
183 |
184 | export {
185 | md5,
186 | md5Pay,
187 | encryptByAES,
188 | decryptByAES,
189 | formatTimestamp,
190 | sortObj,
191 | pennyToYuan,
192 | formatCurrency,
193 | digitUppercase,
194 | handleTemplate
195 | }
196 | // module.exports = util;
197 |
--------------------------------------------------------------------------------
/src/entry/index.dev.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @overview Main Entry
3 | * @author txBoy
4 | * @created 2017-03-21
5 | */
6 |
7 | import React from 'react';
8 | import ReactDOM from 'react-dom';
9 | import {BrowserRouter as Router, BrowserRouter, Route, Link} from 'react-router-dom';
10 | import 'scss/global.scss';
11 |
12 | import { AppContainer } from 'react-hot-loader';
13 |
14 | import App from '../routes/index.js';
15 |
16 |
17 | const render = (Component) => {
18 | ReactDOM.render(
19 |
20 |
21 | ,
22 | document.getElementById('App')
23 | );
24 | };
25 |
26 |
27 | render(App);
28 |
29 | // Hot Module Replacement API
30 | if (module && module.hot) {
31 | module.hot.accept('../routes/index.js', () => {
32 | render(App)
33 | });
34 | }
35 |
--------------------------------------------------------------------------------
/src/entry/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @overview Main Entry
3 | * @author txBoy
4 | * @created 2017-03-21
5 | */
6 |
7 | import React from 'react';
8 | import ReactDOM from 'react-dom';
9 | import {BrowserRouter as Router, BrowserRouter, Route, Link} from 'react-router-dom';
10 | import 'scss/global.scss';
11 |
12 |
13 | import App from '../routes/index.js';
14 |
15 |
16 | ReactDOM.render(
17 | ,
18 | document.getElementById('App')
19 | );
20 |
--------------------------------------------------------------------------------
/src/images/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/libs/index.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Joyotang/React-Koa-Mongodb/aff8ddac9c2065e26619fc5d700af3a7ae61cc52/src/libs/index.js
--------------------------------------------------------------------------------
/src/routes/Account/account.scss:
--------------------------------------------------------------------------------
1 |
2 | .login-module {
3 | width: 400px;
4 | padding: 20px 50px;
5 | margin: 100px auto 0;
6 | border: 1px solid #dbdbdb;
7 | border-radius: 4px;
8 | }
9 |
10 | .login-form-header {
11 | height: 50px;
12 | font-size: 18px;
13 | line-height: 50px;
14 | text-align: center;
15 | }
16 |
17 | .login-form-forgot {
18 | float: right;
19 | }
20 | .account-form-button {
21 | width: 100%;
22 | }
23 |
24 |
25 | .retrieve-module {
26 | width: 400px;
27 | margin: 100px auto 0;
28 | }
29 |
--------------------------------------------------------------------------------
/src/routes/Account/changePwd.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @overview Login
3 | * @author txBoy
4 | * @created 2017-04-13
5 | */
6 |
7 | import { Component } from 'react';
8 | import { Link } from 'react-router-dom';
9 | import { Form, Icon, Input, Button, Checkbox } from 'antd';
10 | const FormItem = Form.Item;
11 |
12 | import './account.scss';
13 |
14 | class ForgetPassword extends Component {
15 | constructor(props) {
16 | super(props)
17 | this.state = {
18 | confirmDirty: false
19 | }
20 | }
21 | handleSubmit = (e) => {
22 | e.preventDefault();
23 | this.props.form.validateFieldsAndScroll((err, values) => {
24 | if (!err) {
25 | console.log('Received values of form: ', values);
26 | }
27 | });
28 | }
29 | handleConfirmBlur = (e) => {
30 | const value = e.target.value;
31 | this.setState({
32 | confirmDirty: this.state.confirmDirty || !!value
33 | });
34 | }
35 | oldPassword = (rule, value, callback) => {
36 | const form = this.props.form;
37 | if (value && this.state.confirmDirty) {
38 | form.validateFields(['oldPassword'], {
39 | force: true
40 | });
41 | }
42 | callback();
43 | }
44 | newPassword = (rule, value, callback) => {
45 | const form = this.props.form;
46 | if (value && this.state.confirmDirty) {
47 | form.validateFields(['newPassword'], {
48 | force: true
49 | });
50 | }
51 | callback();
52 | }
53 | checkPassword = (rule, value, callback) => {
54 | const form = this.props.form;
55 | if (value && value !== form.getFieldValue('password')) {
56 | callback('两次密码不一致!');
57 | } else {
58 | callback();
59 | }
60 | }
61 | render() {
62 | const { getFieldDecorator } = this.props.form;
63 | const formItemLayout = {
64 | labelCol: {
65 | xs: {
66 | span: 24
67 | },
68 | sm: {
69 | span: 6
70 | },
71 | },
72 | wrapperCol: {
73 | xs: {
74 | span: 24
75 | },
76 | sm: {
77 | span: 14
78 | },
79 | },
80 | };
81 | const tailFormItemLayout = {
82 | wrapperCol: {
83 | xs: {
84 | span: 24,
85 | offset: 0,
86 | },
87 | sm: {
88 | span: 14,
89 | offset: 6,
90 | },
91 | },
92 | };
93 | return (
94 |
138 | );
139 | }
140 | }
141 |
142 | const RetrieveFrom = Form.create()(ForgetPassword);
143 |
144 | export default RetrieveFrom;
145 |
--------------------------------------------------------------------------------
/src/routes/Auth/auth.scss:
--------------------------------------------------------------------------------
1 |
2 | .login-module {
3 | width: 400px;
4 | padding: 20px 50px;
5 | margin: 100px auto 0;
6 | border: 1px solid #dbdbdb;
7 | border-radius: 4px;
8 | }
9 |
10 | .login-form-header {
11 | height: 50px;
12 | font-size: 18px;
13 | line-height: 50px;
14 | text-align: center;
15 | }
16 |
17 | .login-form-forgot {
18 | float: right;
19 | }
20 | .account-form-button {
21 | width: 100%;
22 | }
23 |
24 |
25 |
26 | .wrap-register,
27 | .reset-pwd-module {
28 | width: 400px;
29 | margin: 100px auto 0;
30 | }
31 |
32 | .register-module,
33 | .success-module {
34 | &.show {
35 | display: block;
36 | }
37 | &.hide {
38 | display: none;
39 | }
40 | }
41 |
42 | .success-module {
43 |
44 | .complete-text {
45 | font-size: 18px;
46 | text-align: center;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/src/routes/Auth/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @overview Login
3 | * @author txBoy
4 | * @created 2017-04-13
5 | */
6 |
7 | import { Component } from 'react';
8 | import { BrowserRouter as Router, Route, BrowserRouter, Link} from 'react-router-dom';
9 | import { Layout, Menu, Breadcrumb } from 'antd';
10 | const { Header, Content, Footer } = Layout;
11 | import LazyRoute from 'lazy-route';
12 |
13 | /*import LoginFrom from './login';
14 | import ResetPwdForm from './resetPwd';*/
15 |
16 | class AuthLayout extends Component {
17 | handleSubmit = (e) => {
18 | e.preventDefault();
19 | this.props.form.validateFields((err, values) => {
20 | if (!err) {
21 | console.log('Received values of form: ', values);
22 | }
23 | });
24 | }
25 | render() {
26 | return (
27 |
28 |
31 |
32 |
33 | } />
34 | } />
35 | } />
36 |
37 |
38 |
41 |
42 | );
43 | }
44 | }
45 |
46 |
47 | export default AuthLayout;
48 |
--------------------------------------------------------------------------------
/src/routes/Auth/login.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @overview Login
3 | * @author txBoy
4 | * @created 2017-04-13
5 | */
6 |
7 | import { Component } from 'react';
8 | import { Link } from 'react-router-dom';
9 | import { Form, Icon, Input, Button, Checkbox } from 'antd';
10 | const FormItem = Form.Item;
11 |
12 | import fetcher from 'utils/fetcher';
13 | import { md5 } from 'utils/util';
14 | import storage from 'utils/storage.js';
15 | import './auth.scss';
16 |
17 | class Login extends Component {
18 | constructor(props) {
19 | super(props)
20 | this.state = {
21 | userInfo: null
22 | }
23 | }
24 | handleSubmit = (e) => {
25 | e.preventDefault();
26 | this.props.form.validateFields((err, values) => {
27 | if (!err) {
28 | console.log('Received values of form: ', values);
29 | values.password = md5(values.password);
30 | this.onLogin(values)
31 | }
32 | });
33 | }
34 | onLogin(params) {
35 | fetcher.post('/api/user/login', {data: params}).then(res => {
36 | console.log(res)
37 | if (res && res.code === 10000) {
38 | var userInfo = res.data;
39 | storage.setCookie('userInfo', JSON.stringify(userInfo));
40 | window.location.href = '/';
41 | }
42 | })
43 | }
44 | render() {
45 | const { getFieldDecorator } = this.props.form;
46 | return (
47 |
76 | );
77 | }
78 | }
79 |
80 | const LoginForm = Form.create()(Login);
81 |
82 | export default LoginForm;
83 |
--------------------------------------------------------------------------------
/src/routes/Auth/register.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @overview Login
3 | * @author txBoy
4 | * @created 2017-04-13
5 | */
6 |
7 | import { Component } from 'react';
8 | import { Link } from 'react-router-dom';
9 | import { Layout, Menu, Breadcrumb } from 'antd';
10 | const { Header, Content, Footer } = Layout;
11 | import { Form, Icon, Input, Button, Checkbox } from 'antd';
12 | const FormItem = Form.Item;
13 |
14 | import fetcher from 'utils/fetcher';
15 | import { md5 } from 'utils/util';
16 | import './auth.scss';
17 |
18 | class Register extends Component {
19 | constructor(props) {
20 | super(props)
21 | this.state = {
22 | showSuccess: false,
23 | confirmDirty: false,
24 | userInfo: null
25 | }
26 | }
27 | handleSubmit = (e) => {
28 | e.preventDefault();
29 | this.props.form.validateFieldsAndScroll((err, values) => {
30 | if (!err) {
31 | console.log('Received values of form: ', values);
32 | values.password = md5(values.password);
33 | delete values.confirm;
34 | var params = values;
35 | this._onRegiter(params)
36 | }
37 | });
38 | }
39 | handleConfirmBlur = (e) => {
40 | const value = e.target.value;
41 | this.setState({
42 | confirmDirty: this.state.confirmDirty || !!value
43 | });
44 | }
45 | _onRegiter(params) {
46 | fetcher.post('/api/user/register', {data: params}).then(res => {
47 | console.log(res)
48 | if (res.code === 10000) {
49 | var userInfo = res.data;
50 | this.setState({
51 | showSuccess: true,
52 | userInfo: userInfo
53 | })
54 | }
55 | })
56 | }
57 | checkPassword = (rule, value, callback) => {
58 | const form = this.props.form;
59 | if (value && value !== form.getFieldValue('password')) {
60 | callback('Two passwords that you enter is inconsistent!');
61 | } else {
62 | callback();
63 | }
64 | }
65 | checkConfirm = (rule, value, callback) => {
66 | const form = this.props.form;
67 | if (value && this.state.confirmDirty) {
68 | form.validateFields(['confirm'], {
69 | force: true
70 | });
71 | }
72 | callback();
73 | }
74 | render() {
75 | const { getFieldDecorator } = this.props.form;
76 | const formItemLayout = {
77 | labelCol: {
78 | xs: {
79 | span: 24
80 | },
81 | sm: {
82 | span: 6
83 | },
84 | },
85 | wrapperCol: {
86 | xs: {
87 | span: 24
88 | },
89 | sm: {
90 | span: 14
91 | },
92 | },
93 | };
94 | const tailFormItemLayout = {
95 | wrapperCol: {
96 | xs: {
97 | span: 24,
98 | offset: 0,
99 | },
100 | sm: {
101 | span: 14,
102 | offset: 6,
103 | },
104 | },
105 | };
106 | var userInfo = this.state.userInfo;
107 | return (
108 |
109 |
167 |
168 |
169 |
170 |
171 |
172 |
恭喜你{userInfo && userInfo.username},注册成功!
173 |
已发送验证至:{userInfo && userInfo.email}
立即登录
174 |
175 |
176 | );
177 | }
178 | }
179 |
180 | const RegisterForm = Form.create()(Register);
181 |
182 | export default RegisterForm;
183 |
--------------------------------------------------------------------------------
/src/routes/Auth/resetPwd.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @overview Login
3 | * @author txBoy
4 | * @created 2017-04-13
5 | */
6 |
7 | import { Component } from 'react';
8 | import { Link } from 'react-router-dom';
9 | import { Layout, Menu, Breadcrumb } from 'antd';
10 | const { Header, Content, Footer } = Layout;
11 | import { Form, Icon, Input, Button, Checkbox } from 'antd';
12 | const FormItem = Form.Item;
13 |
14 | import './auth.scss';
15 |
16 | class ResetPwd extends Component {
17 | constructor(props) {
18 | super(props)
19 | this.state = {
20 | confirmDirty: false
21 | }
22 | }
23 | handleSubmit = (e) => {
24 | e.preventDefault();
25 | this.props.form.validateFieldsAndScroll((err, values) => {
26 | if (!err) {
27 | console.log('Received values of form: ', values);
28 | }
29 | });
30 | }
31 | handleConfirmBlur = (e) => {
32 | const value = e.target.value;
33 | this.setState({
34 | confirmDirty: this.state.confirmDirty || !!value
35 | });
36 | }
37 | checkPassword = (rule, value, callback) => {
38 | const form = this.props.form;
39 | if (value && value !== form.getFieldValue('password')) {
40 | callback('Two passwords that you enter is inconsistent!');
41 | } else {
42 | callback();
43 | }
44 | }
45 | checkConfirm = (rule, value, callback) => {
46 | const form = this.props.form;
47 | if (value && this.state.confirmDirty) {
48 | form.validateFields(['confirm'], {
49 | force: true
50 | });
51 | }
52 | callback();
53 | }
54 | render() {
55 | const { getFieldDecorator } = this.props.form;
56 | const formItemLayout = {
57 | labelCol: {
58 | xs: {
59 | span: 24
60 | },
61 | sm: {
62 | span: 6
63 | },
64 | },
65 | wrapperCol: {
66 | xs: {
67 | span: 24
68 | },
69 | sm: {
70 | span: 14
71 | },
72 | },
73 | };
74 | const tailFormItemLayout = {
75 | wrapperCol: {
76 | xs: {
77 | span: 24,
78 | offset: 0,
79 | },
80 | sm: {
81 | span: 14,
82 | offset: 6,
83 | },
84 | },
85 | };
86 | return (
87 |
132 | );
133 | }
134 | }
135 |
136 | const ResetPwdForm = Form.create()(ResetPwd);
137 |
138 | export default ResetPwdForm;
139 |
--------------------------------------------------------------------------------
/src/routes/IndexPage/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @overview IndexPage
3 | * @author txBoy
4 | * @created 2017-03-21
5 | */
6 |
7 | import { Component } from 'react';
8 |
9 | // import logo from 'img/logo.svg';
10 | import 'scss/global.scss';
11 |
12 | class IndexPage extends Component {
13 | constructor(props) {
14 | super(props)
15 | this.state = {};
16 | }
17 | componentDidMount() {
18 |
19 | }
20 | render() {
21 | return (
22 |
25 | );
26 | }
27 | }
28 |
29 | export default IndexPage;
30 |
--------------------------------------------------------------------------------
/src/routes/LayoutPage/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @overview layoutPage
3 | * @author txBoy
4 | * @created 2017-04-05
5 | */
6 |
7 | import { Component } from 'react';
8 | import { Link } from 'react-router-dom';
9 |
10 | import { Layout, Menu, Breadcrumb, Icon, Button } from 'antd';
11 | const { Header, Content, Footer, Sider } = Layout;
12 | const SubMenu = Menu.SubMenu;
13 |
14 | import { removeWithoutCopy } from 'utils/util';
15 | import fetcher from 'utils/fetcher';
16 | import storage from 'utils/storage.js';
17 | import 'scss/global.scss';
18 |
19 | const userInfo = JSON.parse(storage.getCookie('userInfo') || '{}');
20 |
21 | class LayoutPage extends Component {
22 | constructor(props) {
23 | super(props)
24 | this.state = {
25 | isRenderSider: true,
26 | menuConfig: this.getNav(),
27 | collapsed: false,
28 | mode: 'inline',
29 | };
30 | }
31 | componentDidMount () {
32 | //this.formatMenuConf()
33 | }
34 | onCollapse = (collapsed) => {
35 | console.log(collapsed);
36 | this.setState({
37 | collapsed,
38 | mode: collapsed ? 'vertical' : 'inline',
39 | });
40 | }
41 | getNav () {
42 | var menuConfig = this.formatMenuConf();
43 | return menuConfig.map((x) => {
44 | return {
45 | key: x.path,
46 | name: x.name,
47 | icon: x.icon,
48 | subs: (x.childRoutes || []).map((y, i) => {
49 | let subKey = y.path.substr(1).replace(/\//, '-');
50 | return {
51 | key: subKey,
52 | name: y.name,
53 | path: y.path
54 | }
55 | })
56 | }
57 | })
58 | }
59 | onLogout() {
60 | fetcher.get('/api/user/logout', function (err, res) {
61 | console.log(res)
62 | })
63 | storage.setCookie('userInfo', '', 0);
64 | window.location.href = '/login';
65 | }
66 | formatMenuConf () {
67 | var routes = this.props.menuConfig;
68 | let list = routes.slice(0);
69 | list.forEach((x, key) => {
70 | (x.childRoutes || []).map( y => {
71 | if (y.path.indexOf(':') > 0) {
72 | removeWithoutCopy(x.childRoutes, y);
73 | }
74 | })
75 | })
76 | return list;
77 | }
78 | getMenuKeys () {
79 | var path = location.pathname.replace(/^\//, '');
80 | var menuKeys = [];
81 | if (!path) {
82 | menuKeys.push('index');
83 | } else {
84 | menuKeys = path.split('/').slice(0, 2);
85 | }
86 | return menuKeys;
87 | }
88 | renderMenu (menuConfig) {
89 | var navs = menuConfig.slice(1);
90 | return navs.map(x => {
91 | if (x.path === '/') {
92 | return null;
93 | } else {
94 | return (
95 | {x.name || x.key}}
98 | >
99 | {(x.subs || []).map(y => (
100 | {y.name}
101 | ))}
102 |
103 | );
104 | }
105 | });
106 | }
107 | render() {
108 | var menuConfig = this.state.menuConfig;
109 | // console.log(menuConfig)
110 | var menuKeys = this.getMenuKeys();
111 | return (
112 |
113 |
118 | React-Node-Demo
119 |
130 |
131 |
132 |
133 | Hello, 欢迎 {userInfo.realName || userInfo.username} ^_^
134 | 乐潇游【React-Node-Mongodb】技术栈实例平台
135 |
138 |
139 |
140 |
141 | User
142 | Content
143 |
144 |
145 | {this.props.routesChildren}
146 |
147 |
148 |
149 |
150 |
151 | );
152 | }
153 | }
154 |
155 | export default LayoutPage;
156 |
--------------------------------------------------------------------------------
/src/routes/User/detail.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @overview Detail
3 | * @author txBoy
4 | * @created 2017-03-21
5 | */
6 |
7 | import { Component } from 'react';
8 |
9 | class DetailPage extends Component {
10 | render() {
11 | return (
12 | 用户姓名: {this.props.match.params.name}
13 | );
14 | }
15 | }
16 |
17 | export default DetailPage;
18 |
--------------------------------------------------------------------------------
/src/routes/User/list.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @overview List
3 | * @author txBoy
4 | * @created 2017-03-21
5 | */
6 |
7 | import { Component } from 'react';
8 | import { Link } from 'react-router-dom';
9 | import { Table } from 'antd';
10 | import fetcher from 'utils/fetcher';
11 | import { md5, formatTimestamp, formatGender } from 'utils/util';
12 |
13 | class ListPage extends Component {
14 | constructor(props) {
15 | super(props)
16 | this.state = {
17 | query: {
18 | page: 1,
19 | size: 10,
20 | filter: {
21 | searchValue: null
22 | }
23 | },
24 | data: null,
25 | userList: []
26 | }
27 | }
28 | componentDidMount() {
29 |
30 | this.getList();
31 |
32 | var timestamp = new Date().getTime();
33 | console.log(formatTimestamp(timestamp))
34 | }
35 | // 获取列表数据
36 | getList() {
37 | var params = Object.assign({}, this.state.query);
38 | params.filter = JSON.stringify(params.filter);
39 |
40 | fetcher.get('/api/user/getUsers', {data: params}).then(res => {
41 | console.log(res)
42 | if (res && res.code === 10000) {
43 | var data = res.data;
44 | var userList = data.list || [];
45 | this.setState({
46 | data: data,
47 | userList: userList
48 | })
49 | }
50 | })
51 | }
52 |
53 | renderTable() {
54 | var _this = this;
55 | var data = this.state.data;
56 | const pagination = {
57 | current: _this.state.query.page,
58 | total: data && data.total || 0,
59 | showSizeChanger: true,
60 | onShowSizeChange(current, pageSize) {
61 | _this.state.query.page = current;
62 | _this.state.query.size = pageSize;
63 | _this.forceUpdate();
64 | _this.getList();
65 | },
66 | onChange(current) {
67 | _this.state.query.page = current;
68 | _this.getList();
69 | }
70 | };
71 |
72 | const columns = [{
73 | title: '用户名',
74 | dataIndex: 'username',
75 | render: (text, item) => {text}
76 | }, {
77 | title: '性别',
78 | dataIndex: 'gender',
79 | render: (text, item) => formatGender(item.gender)
80 | }, {
81 | title: '邮箱',
82 | dataIndex: 'email',
83 | }, {
84 | title: '操作',
85 | dataIndex: 'uid',
86 | render: (text, item) => (
87 |
88 | Action - {item.uid}
89 |
90 | Delete
91 |
92 | )
93 | }];
94 |
95 | // rowSelection object indicates the need for row selection
96 | const rowSelection = {
97 | onChange: (selectedRowKeys, selectedRows) => {
98 | console.log(`selectedRowKeys: ${selectedRowKeys}`,
99 | 'selectedRows: ', selectedRows);
100 | },
101 | onSelect: (record, selected, selectedRows) => {
102 | console.log(record, selected, selectedRows);
103 | },
104 | onSelectAll: (selected, selectedRows, changeRows) => {
105 | console.log(selected, selectedRows, changeRows);
106 | },
107 | getCheckboxProps: record => ({
108 | //disabled: record.name === 'Disabled User', // Column configuration not to be checked
109 | }),
110 | };
111 |
112 | return ()
113 | }
114 |
115 | render() {
116 | return (
117 |
118 | {this.renderTable()}
119 |
120 | );
121 | }
122 | }
123 |
124 | export default ListPage;
125 |
--------------------------------------------------------------------------------
/src/routes/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @overview Router Index
3 | * @author txBoy
4 | * @created 2017-03-21
5 | */
6 | import React, { Component } from 'react';
7 | import { BrowserRouter as Router, Route } from 'react-router-dom';
8 | import LazyRoute from 'lazy-route';
9 |
10 | import AuthLayout from './Auth';
11 | import LayoutPage from './LayoutPage';
12 |
13 | import routesConfig from './routes';
14 | import storage from 'utils/storage';
15 |
16 | const userInfo = storage.getCookie('userInfo') ? JSON.parse(storage.getCookie('userInfo')) : '';
17 | const accessReg = userInfo.accessUrls && userInfo.accessUrls.length ? new RegExp(userInfo.accessUrls.map(url => '^' + url).join('|')) : null;
18 |
19 | // 定义路由组件
20 | const RouteWithSubRoutes = (route) => (
21 | {
22 | // route.onEnter(); // 校验准入权限
23 | return ;
24 | }}/>
25 | )
26 |
27 | // 渲染所有路由
28 | const renderRoutes = (routes) => {
29 | return routes.map((route, i) => {
30 | if(route.childRoutes) {
31 | return renderRoutes(route.childRoutes)
32 | } else {
33 | return ()
34 | }
35 | })
36 | }
37 |
38 | function checkAccess() {
39 | var pathname = window.location.pathname;
40 | console.log('-----------pathname:', pathname)
41 | if (pathname != '/' && accessReg && !accessReg.test(pathname)) {
42 | location.replace('/error/403');
43 | }
44 | }
45 |
46 | function formatRoutes(routes) {
47 | let list = routes.slice(0);
48 | while (list.length) {
49 | let route = list.shift();
50 | if (!route.onEnter) {
51 | route.onEnter = checkAccess;
52 | }
53 | if (route.childRoutes && route.childRoutes.length) {
54 | list.push.apply(list, route.childRoutes);
55 | }
56 | }
57 | return routes;
58 | }
59 |
60 | var routes = formatRoutes(routesConfig);
61 |
62 | class App extends Component {
63 | constructor(props) {
64 | super(props)
65 | this.state = {
66 | isRenderSider: false,
67 | simplePage: false
68 | };
69 | }
70 | componentWillMount() {
71 | var pathname = window.location.pathname;
72 | if(pathname.indexOf('/login') > -1){
73 | storage.setCookie('userInfo', '');
74 | }
75 | // this.hideSider();
76 | }
77 | hideSider() {
78 | var pathname = window.location.pathname;
79 | var pathArr = ['login', 'register', 'resetPwd', 'error']
80 | for (var i = 0; i < pathArr.length; i++) {
81 | if (pathname.indexOf(pathArr[i]) != -1) {
82 | this.setState({
83 | isRenderSider: false
84 | })
85 | }
86 | }
87 | }
88 | renderLayoutPage() {
89 | var pathname = window.location.pathname;
90 | if(userInfo){
91 | this.state.isRenderSider = true;
92 | }
93 | if (pathname.indexOf('error') != -1) {
94 | this.state.simplePage = true;
95 | }
96 | var isRender = this.state.isRenderSider;
97 | var simplePage = this.state.simplePage;
98 | if (isRender && !simplePage) {
99 | return (
100 |
101 | )
102 | } else if (simplePage) {
103 | return (
104 |
105 | )
106 | } else {
107 | var pathname = window.location.pathname;
108 | if(pathname.indexOf('/login') > -1 || pathname.indexOf('/register') > -1 || pathname.indexOf('/resetPwd') > -1){
109 | return (
110 |
111 | )
112 | } else {
113 | window.location.href = '/login';
114 | }
115 | }
116 | }
117 | render() {
118 | return (
119 |
120 | {this.renderLayoutPage()}
121 |
122 | );
123 | }
124 | }
125 | export default App;
126 |
--------------------------------------------------------------------------------
/src/routes/index.x.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @overview Router Index
3 | * @author txBoy
4 | * @created 2017-03-21
5 | */
6 |
7 | import React, { Component } from 'react';
8 | import { BrowserRouter as Router, Route, BrowserRouter, Link} from 'react-router-dom';
9 | import LazyRoute from 'lazy-route';
10 |
11 | import AuthLayout from './Auth';
12 | /*import ResetPwd from './Auth/resetPwd';*/
13 | import LayoutPage from './LayoutPage';
14 |
15 | import routesConfig from './routes';
16 |
17 | // 定义路由组件
18 | const RouteWithSubRoutes = (route) => (
19 | (
20 |
21 | )}/>
22 | )
23 |
24 | // 渲染所有路由
25 | const renderRoutes = (routes) => {
26 | return routes.map((route, i) => {
27 | if(route.childRoutes) {
28 | return renderRoutes(route.childRoutes)
29 | } else {
30 | return ()
31 | }
32 | })
33 | }
34 |
35 | class App extends Component {
36 | constructor(props) {
37 | super(props)
38 | this.state = {
39 | isRenderSider: true,
40 | };
41 | }
42 | componentWillMount() {
43 | this.hideSider();
44 | }
45 | hideSider() {
46 | var pathname = window.location.pathname;
47 | var pathArr = ['login', 'register', 'resetPwd']
48 | for (var i = 0; i < pathArr.length; i++) {
49 | if (pathname.indexOf(pathArr[i]) >= 0) {
50 | this.setState({
51 | isRenderSider: false
52 | })
53 | }
54 | }
55 | console.log(location.pathname)
56 | }
57 | renderLayoutPage() {
58 | var isRender = this.state.isRenderSider;
59 | if (isRender) {
60 | return (
61 |
62 | )
63 | } else {
64 | return (
65 |
68 | )
69 | }
70 | }
71 | render() {
72 | return (
73 |
74 | {this.renderLayoutPage()}
75 |
76 | );
77 | }
78 | }
79 |
80 |
81 | //
82 | // }/>
83 | // }/>
84 |
85 | export default App;
86 |
--------------------------------------------------------------------------------
/src/routes/routes.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @overview Router Config
3 | * @author txBoy
4 | * @created 2017-03-21
5 | */
6 |
7 | const routesConfig = [
8 | {
9 | path: '/',
10 | exact: true,
11 | icon: 'home',
12 | name: '首页',
13 | component: './IndexPage/index'
14 | },
15 | {
16 | path: 'user',
17 | icon: 'user',
18 | name: '用户',
19 | childRoutes: [
20 | {
21 | name: '列表',
22 | path: '/user/list',
23 | component: './User/list'
24 | },
25 | {
26 | name: '详情',
27 | path: '/user/detail/:name',
28 | component: './User/detail'
29 | }
30 | ]
31 | },
32 | {
33 | path: 'account',
34 | icon: 'solution',
35 | name: '账户',
36 | childRoutes: [
37 | {
38 | name: '修改密码',
39 | path: '/account/changePwd',
40 | component: './Account/changePwd'
41 | }
42 | ]
43 | }
44 | ]
45 |
46 | export default routesConfig;
47 |
--------------------------------------------------------------------------------
/src/scss/animation.scss:
--------------------------------------------------------------------------------
1 | /* success animation */
2 | @keyframes success-short-Animation {
3 | 0% {
4 | width: 0;
5 | left: 1px;
6 | top: 19px
7 | }
8 | 54% {
9 | width: 0;
10 | left: 1px;
11 | top: 19px
12 | }
13 | 70% {
14 | width: 50px;
15 | left: -4px;
16 | top: 37px
17 | }
18 | 84% {
19 | width: 17px;
20 | left: 21px;
21 | top: 48px
22 | }
23 | 100% {
24 | width: 25px;
25 | left: 14px;
26 | top: 45px
27 | }
28 | }
29 | @keyframes success-long-Animation {
30 | 0% {
31 | width: 0;
32 | right: 46px;
33 | top: 54px
34 | }
35 | 65% {
36 | width: 0;
37 | right: 46px;
38 | top: 54px
39 | }
40 | 84% {
41 | width: 55px;
42 | right: 0;
43 | top: 35px
44 | }
45 | 100% {
46 | width: 47px;
47 | right: 8px;
48 | top: 38px
49 | }
50 | }
51 | /* lose animation */
52 | @keyframes lose-Animation {
53 | 0% {
54 | transform: scale(0.6);
55 | opacity: .2
56 | }
57 | 50% {
58 | transform: scale(0.6);
59 | opacity: .5
60 | }
61 | 80% {
62 | transform: scale(1.15);
63 | opacity: .8
64 | }
65 | 100% {
66 | transform: scale(1);
67 | opacity: 1.0
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/scss/global.scss:
--------------------------------------------------------------------------------
1 | table { border-spacing:0; border-collapse:collapse;}
2 |
3 | .wrap {
4 | display: flex;
5 | height: 100%;
6 | }
7 |
8 | .simple-wrap {
9 | display: flex;
10 | width: 100%;
11 | }
12 |
13 | /* float and clearfix */
14 | .flt {
15 | float: left;
16 | }
17 |
18 | .frt {
19 | float: right;
20 | }
21 | .clearfix:before,
22 | .clearfix:after {content: " "; display: table;}
23 | .clearfix:after {clear: both;}
24 | .clearfix {*zoom: 1;}
25 |
26 | .ant-layout-sider .logo {
27 | height: 32px;
28 | font-size: 14px;
29 | line-height: 32px;
30 | color: #fff;
31 | text-align: center;
32 | white-space: nowrap;
33 | text-overflow: ellipsis;
34 | overflow: hidden;
35 | background: #333;
36 | border-radius: 6px;
37 | margin: 16px;
38 | }
39 |
40 | .ant-layout-sider .ant-layout-sider-collapsed .anticon {
41 | font-size: 16px;
42 | margin-left: 8px;
43 | }
44 |
45 | .ant-layout-sider .ant-layout-sider-collapsed .nav-text {
46 | display: none;
47 | }
48 |
49 | .ant-layout-sider .ant-layout-sider-collapsed .ant-menu-submenu-vertical > .ant-menu-submenu-title:after {
50 | display: none;
51 | }
52 |
53 | .ant-layout-header {
54 | display: flex;
55 | justify-content: space-between;
56 | padding: 0 20px !important;
57 | font-size: 16px;
58 | color: #fff;
59 | background: #454545 !important;
60 | .user-info {}
61 | .header-content {
62 | flex-grow: 1;
63 | text-align: center;
64 | }
65 | .login-box {
66 | .logout-btn {
67 | padding: 5px 15px;
68 | font-size: 14px;
69 | color: #fff;
70 | border: 1px solid #fff;
71 | border-radius: 4px;
72 | &:hover {
73 | color: #108ee9;
74 | border-color: #108ee9;
75 | }
76 | }
77 | }
78 | }
79 |
80 | .ant-layout-breadcrumb {
81 | margin: 12px 0;
82 | }
83 |
84 | .index-content {
85 | .welcome-text {
86 | margin-top: 15px;
87 | font-size: 18px;
88 | text-align: center;
89 | }
90 | }
91 |
92 |
93 | /* text style */
94 | .text-blue,
95 | .text-normal, {
96 | color: #108ee9;
97 | }
98 |
99 | .text-success {
100 | color: #6c0;
101 | }
102 |
103 | .text-red,
104 | .text-error {
105 | color: #f00;
106 | }
107 |
108 | .text-warn {
109 | color: #ffbf00;
110 | }
111 |
112 | .text-center {
113 | text-align: center;
114 | }
115 |
116 |
117 | /* icon */
118 | .iconbox {
119 | position: relative;
120 | width: 88px;
121 | height: 88px;
122 | border-radius: 50px;
123 | border: 4px solid #6c3;
124 | margin: 15px auto;
125 | }
126 | .icon-lose {border-color: #fc454c;}
127 | .icon-success .line-short {
128 | width: 25px;
129 | height: 5px;
130 | position: absolute;
131 | left: 14px;
132 | top: 46px;
133 | border-radius: 4px;
134 | background-color: #6c3;
135 | -webkit-transform: rotate(45deg);
136 | -moz-transform: rotate(45deg);
137 | transform: rotate(45deg);
138 | -webkit-animation: success-short-Animation .65s;
139 | -moz-animation: success-short-Animation .65s;
140 | animation: success-short-Animation .65s;
141 | }
142 | .icon-success .line-long {
143 | width: 45px;
144 | height: 5px;
145 | position: absolute;
146 | right: 8px;
147 | top: 38px;
148 | border-radius: 4px;
149 | background-color: #6c3;
150 | -webkit-transform: rotate(-45deg);
151 | -moz-transform: rotate(-45deg);
152 | transform: rotate(-45deg);
153 | -webkit-animation: success-long-Animation .65s;
154 | -moz-animation: success-long-Animation .65s;
155 | animation: success-long-Animation .65s;
156 | }
157 | .losebox {
158 | position: relative;
159 | width: 88px;
160 | height: 88px;
161 | margin: 0 auto;
162 | text-align: center;
163 | }
164 | .losebox {
165 | -webkit-animation: lose-Animation .5s;
166 | -moz-animation: lose-Animation .5s;
167 | animation: lose-Animation .5s;
168 | }
169 | .icon-lose .line-left, .icon-lose .line-right {
170 | position: absolute;
171 | left: 17px;
172 | top: 37px;
173 | width: 47px;
174 | height: 5px;
175 | border-radius: 4px;
176 | background: #fc454c;
177 | -webkit-transform: rotate(45deg);
178 | -moz-transform: rotate(45deg);
179 | transform: rotate(45deg);
180 | }
181 | .icon-lose .line-right {
182 | right: 11px;
183 | top: 37px;
184 | -webkit-transform: rotate(-45deg);
185 | -moz-transform: rotate(-45deg);
186 | transform: rotate(-45deg);
187 | }
188 |
--------------------------------------------------------------------------------
/src/utils/fetcher.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @overview 封装fetch
3 | * @author txBoy
4 | * @created 2017-03-14
5 | */
6 |
7 | import xFetch from './xfetch.js';
8 |
9 | var fetcher = {};
10 |
11 | /**
12 | * 项目fetch请求方法的封装,涉及到API请求的地方都走这里, 便于统一处理
13 | * @param url
14 | * @param options
15 | */
16 | fetcher.request = (function () {
17 | var memCache = {};
18 | return function (url, options) {
19 | if (options.memCache) {
20 | var key = url + JSON.stringify(options.data);
21 | var cache = memCache[key];
22 | }
23 | if (cache) {
24 | return new Promise(function (resolve) {
25 | setTimeout(resolve.bind(null, cache), 0);
26 | })
27 | } else {
28 | const headers = {};
29 | headers['Content-Type'] = options.contentType || 'application/json';
30 | options.headers = Object.assign((options && options.headers) || {}, headers);
31 |
32 | let data = options && options.data || {};
33 | // 公司业务需要的统一参数,如不需要可删除
34 | /*data.source = data.source || 'web';
35 | data.sv = data.sv || '1.0';
36 | data.version = data.version || '1.0';
37 | data.macId = data.macId || 'WEB';*/
38 |
39 | options.body = data;
40 | delete options.data;
41 | delete options.memCache;
42 |
43 | return xFetch(url, options).then( (res) => {
44 | if (res.status === 'success' && options.memCache) {
45 | memCache[key] = res;
46 | }
47 | if (typeof options.success === 'function') {
48 | options.success(res);
49 | }
50 | return res;
51 | });
52 | }
53 | }
54 | })();
55 |
56 | /**
57 | * fetcher get 方法封装
58 | * @param url
59 | * @param options //特殊接口需要headers可通过options传递
60 | * @param callback //可省略,如果省略则通过then获取回调,推荐使用通过Promise.then方式获取回调
61 | * @returns Promise
62 | * @example fetcher.get('/api/getList', {data: {page:1, size:10} }).then(res => {cosnole.log(res)})
63 | */
64 | fetcher.get = function (url, options, success) {
65 | return this.request(url, {
66 | method: 'GET',
67 | headers: options && options.headers,
68 | contentType: options && options.contentType,
69 | data: options && options.data,
70 | success: success
71 | });
72 | }
73 |
74 | /**
75 | * fetcher post 方法封装
76 | * @param url
77 | * @param params
78 | * @param callback //可省略,如果省略则通过then获取回调,推荐使用通过Promise.then方式获取回调
79 | * @returns Promise
80 | * @example fetcher.post('/api/edit', {data: {name:'txBoy'} }).then(res => {cosnole.log(res)})
81 | */
82 | fetcher.post = function (url, options, success) {
83 | return this.request(url, {
84 | method: 'POST',
85 | headers: options && options.headers,
86 | contentType: options && options.contentType,
87 | data: options && options.data,
88 | success: success
89 | });
90 | }
91 |
92 | fetcher.all = function (urls, options) {
93 | var options = options || {};
94 | var method = options.method || 'GET';
95 | var data = options.data;
96 |
97 | if (method === 'POST') {
98 | return Promise.all(urls.map(url => {
99 | return fetcher.post(url, data);
100 | }))
101 | } else {
102 | return Promise.all(urls.map(url => {
103 | return fetcher.get(url, data);
104 | }));
105 | }
106 | }
107 |
108 |
109 | export default fetcher;
110 |
--------------------------------------------------------------------------------
/src/utils/storage.js:
--------------------------------------------------------------------------------
1 | var Storage = {
2 | setSession: function(key, val) {
3 | try {
4 | var val_str = JSON.stringify(val || {});
5 | window.sessionStorage.setItem('$' + key, val_str);
6 | }
7 | catch (ex) {
8 | console.error(ex);
9 | }
10 | },
11 | getSession: function(key) {
12 | try {
13 | var val = window.sessionStorage.getItem('$' + key) || '{}';
14 | return JSON.parse(val);
15 | }
16 | catch (ex) {
17 | console.error(ex);
18 | }
19 | },
20 | setLocal: function(key, val) {
21 | try {
22 | var val_str = JSON.stringify(val || {});
23 | window.localStorage.setItem('$' + key, val_str);
24 | }
25 | catch (ex) {
26 | console.error(ex);
27 | }
28 | },
29 | getLocal: function(key) {
30 | try {
31 | var val = window.localStorage.getItem('$' + key);
32 | return val ? JSON.parse(val):'';
33 | }
34 | catch (ex) {
35 | console.error(ex);
36 | }
37 | },
38 | delLocal: function(key) {
39 | try {
40 | window.localStorage.removeItem('$' + key);
41 | return;
42 | }
43 | catch (ex) {
44 | console.error(ex);
45 | }
46 | },
47 | setCookie: function(name, value, ds) {
48 | var Days = ds ? ds:30;
49 | var exp = new Date();
50 | exp.setTime(exp.getTime() + Days*24*60*60*1000);
51 | document.cookie = name + "="+ escape (value) + ";expires=" + exp.toGMTString()+";path=/;";
52 | },
53 | getCookie: function(name) {
54 | var arr,reg=new RegExp("(^| )"+name+"=([^;]*)(;|$)");
55 | if(arr=document.cookie.match(reg))
56 | return decodeURIComponent(arr[2]);
57 | else {
58 | return null;
59 | }
60 | }
61 | };
62 |
63 | module.exports = Storage;
64 |
--------------------------------------------------------------------------------
/src/utils/util.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @overview 封装util
3 | * @author txBoy
4 | * @created 2017-03-23
5 | */
6 |
7 | import crypto from 'crypto';
8 |
9 | /***
10 | * 去除字符串两侧空格
11 | * @param str String 字符串
12 | */
13 | function trim(str) {
14 | return str.replace(/(^\s+)|(\s+$)/g, '');
15 | };
16 |
17 | /**
18 | * md5正常字符串加密
19 | */
20 | function md5(str) {
21 | var ret = crypto.createHash('md5').update(str.toString()).digest('hex');
22 | return ret;
23 | }
24 |
25 | /**
26 | * md5中文字符串加密
27 | * @desc 解决中文加密不同的问题
28 | */
29 | function md5Pay(str) {
30 | str = (new Buffer(str)).toString("binary");
31 | var ret = crypto.createHash('md5').update(str).digest("hex");
32 | return ret;
33 | }
34 |
35 | /**
36 | * 验证手机号码的方法
37 | * @param phoneNo 传入号码
38 | * @return Boolean
39 | */
40 | function isPhone(phoneNo) {
41 | var reg = /^1(3|4|5|7|8)[0-9]\d{8}$/;
42 | if (!res.test(phoneNo)) {
43 | return false;
44 | }
45 | return true;
46 | }
47 |
48 | /**
49 | * 获取日期字符串
50 | * @param {addDayCount) 获取AddDayCount天后的日期
51 | * @return String 格式为:YYYY-MM-DD
52 | * @example getDateStr(0) //今天 getDateStr(-1) //昨天
53 | */
54 | function getDateStr(addDayCount) {
55 | var dd = new Date();
56 | dd.setDate(dd.getDate() + addDayCount);
57 | var y = dd.getFullYear();
58 | var m = dd.getMonth() + 1;
59 | var d = dd.getDate();
60 | m = m < 10 ? '0' + m : m;
61 | d = d < 10 ? '0' + d : d;
62 | return y + "-" + m + "-" + d;
63 | };
64 |
65 | /**
66 | * 格式化时间戳 - 时间毫秒数转化为固定格式的时间字符串
67 | * @param timestamp
68 | * @param dateFormate
69 | * @description 其中dateFormate是有YYYY MM DD hh mm ss组成,可以是YYYY-MM-DD 也可以是YYYY-MM-DD hh:mm:ss
70 | * 如果不传的话,默认为: YYYY-MM-DD hh:mm:ss
71 | * @example formatTimestamp(1464314911403);
72 | * formatTimestamp(1464314911403, 'yyyyMMddhhmmss');
73 | * formatTimestamp(1464314911403,'YYYY-MM-DD');
74 | */
75 | function formatTimestamp(timestamp, dateFormate) {
76 | var datemills = parseInt(timestamp);
77 | if (isNaN(datemills)) {
78 | console.error("Wrong timestamp, must be a number!");
79 | return;
80 | }
81 | if (dateFormate == undefined) {
82 | dateFormate = "YYYY-MM-DD hh:mm:ss";
83 | }
84 | var date = new Date(datemills);
85 | var year = "" + date.getFullYear();
86 | var month = (date.getMonth() > 8 ? (date.getMonth() + 1) : "0" + (date.getMonth() + 1));
87 | var day = date.getDate() > 9 ? date.getDate() : "0" + date.getDate();
88 | var hour = date.getHours() > 9 ? date.getHours() : "0" + date.getHours();
89 | var minute = date.getMinutes() > 9 ? date.getMinutes() : "0" + date.getMinutes();
90 | var second = date.getSeconds() > 9 ? date.getSeconds() : "0" + date.getSeconds();
91 | var datestr = dateFormate.replace(/yyyy/gi, year).replace('MM', month).replace(/dd/gi, day)
92 | .replace("hh", hour).replace("mm", minute).replace("ss", second);
93 | return datestr;
94 | }
95 |
96 |
97 | /**
98 | * 格式化卡号
99 | * @param {cardNo) 卡号
100 | * @return {String}
101 | * @example formatCardNo(201622512580888818) //2016 2251 2580 8888 18
102 | */
103 | function formatCardNo(cardNo) {
104 | if (cardNo) {
105 | return cardNo.replace(/(\d{4})(\d{4})(\d{4})(\d{4})(\d{2})/, '$1-$2-$3-$4-$5');
106 | }
107 | return '未知卡号';
108 | };
109 |
110 | /**
111 | * 格式化手机号码
112 | * @param {phoneNo) 手机号
113 | * @return {String}
114 | * @example formatCardNo(17722512580) //177 2251 2580
115 | */
116 | function formatTelPhone(phone) {
117 | return phone.replace(/(\d{3})(\d{4})(\d{4})/, '$1 $2 $3');
118 | };
119 |
120 | /**
121 | * 数组去重
122 | */
123 | function arrayUnique(arr) {
124 | var result = [], hash = {};
125 | for (var i = 0, elem; (elem = arr[i]) != null; i++) {
126 | if (!hash[elem]) {
127 | result.push(elem);
128 | hash[elem] = true;
129 | }
130 | }
131 | return result;
132 | };
133 |
134 | /**
135 | * 移除数组中指定元素,改变原数组
136 | * @param {arr} 需要处理的数组
137 | * @param {item} 该数组中要移除的元素
138 | */
139 | function removeWithoutCopy(arr, item) {
140 | for (var i = 0; i < arr.length; i++) {
141 | if (arr[i] == item) {
142 | arr.splice(i, 1);
143 | i--;
144 | }
145 | }
146 | return arr;
147 | }
148 |
149 | /**
150 | * 格式化性别
151 | */
152 | function formatGender(type) {
153 | switch (type) {
154 | case 0:
155 | return '保密';
156 | break;
157 | case 1:
158 | return '男';
159 | break;
160 | case 2:
161 | return '女';
162 | break;
163 | case 3:
164 | return '保密';
165 | break;
166 | default :
167 | return '未知'
168 | }
169 | }
170 |
171 | /**
172 | * URL参数转JSON
173 | * @param {url}
174 | * @return {JSONString}
175 | * @example var url = "http://www.example.com/user/getInfo?username&sid=12580&t";
176 | * getJSONByURL(url) // {"username":"txboy","sid":"12580", "t": undefined}
177 | */
178 | function getJSONByURL(url) {
179 | if(url == ''){
180 | url = window.location.href;
181 | }
182 | var index = url.indexOf('?');
183 | var str = url.substr(index + 1);
184 | var arr = str.split('&');
185 | var json = '{';
186 | for (var i = 0; i < arr.length; i++){
187 | var s = arr[i].indexOf('=');
188 | if(s == -1){
189 | json += ',"' + arr[i] + '":"undefined"';
190 | continue;
191 | }
192 | var key = arr[i].substr(0, s);
193 | var val = arr[i].substr(s+1);
194 | var comma = ',';
195 | if(i == 0) {
196 | comma = '';
197 | }
198 | json += comma + '\"' + key + '\":' + '\"' + val + '\"';
199 | }
200 | return json += '}';
201 | }
202 |
203 | export {
204 | trim,
205 | md5,
206 | md5Pay,
207 | isPhone,
208 | getDateStr,
209 | formatTimestamp,
210 | formatCardNo,
211 | formatTelPhone,
212 | arrayUnique,
213 | removeWithoutCopy,
214 | formatGender,
215 | getJSONByURL
216 | }
217 |
--------------------------------------------------------------------------------
/src/utils/xfetch.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @overview 封装fetch
3 | * @author txBoy
4 | * @created 2017-03-14
5 | */
6 |
7 | import 'es6-promise';
8 | import 'whatwg-fetch';
9 | import 'fetch-detector';
10 | // import 'fetch-ie8';
11 | // import 'formdata-emulate';
12 | import queryString from 'querystring';
13 |
14 | const encodeReg = /application\/x-www-form-urlencoded/;
15 | const JSONReg = /application\/json/;
16 |
17 | function checkStatus(response) {
18 | if (response.status >= 200 && response.status < 300) {
19 | return response;
20 | } else {
21 | var error = new Error(response.statusText);
22 | error.status = response.status;
23 | error.code = -500; //用-500标示http错误,非业务逻辑异常
24 | throw error;
25 | }
26 | }
27 |
28 | function parseJSON(res) {
29 | if (res.status === 200) {
30 | return res.json();
31 | } else {
32 | return {
33 | status: 'error',
34 | msg: '网络异常,请稍后再试!',
35 | code: -500
36 | };
37 | }
38 | }
39 |
40 | /**
41 | * fetch api的包装
42 | * @param url 请求url
43 | * @param options
44 | * - method 请求方法
45 | * - body 请求参数,get请求会将请求参数拼接到url, post请求则会放到body
46 | * @returns {Promise}
47 | */
48 | function BaseFetch(url, options) {
49 | options = options || {};
50 | if (options.method) options.method = options.method.toUpperCase();
51 | if (!options.credentials) options.credentials = 'include';
52 |
53 | var headers = {
54 | "X-Requested-With": "XMLHttpRequest",
55 | "Accept": "application/json"
56 | }
57 | options.headers = Object.assign(options.headers || {}, headers);
58 |
59 | if (options.method === 'POST') {
60 | if (typeof options.body === 'object' && !(options.body instanceof FormData)) {
61 | let type = options.headers['Content-Type'];
62 | if (JSONReg.test(type)) {
63 | options.body = JSON.stringify(options.body);
64 | } else if (encodeReg.test(type) || !type) {
65 | options.headers['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';
66 | options.body = queryString.stringify(options.body);
67 | }
68 | }
69 | } else {
70 | if (typeof options.body === 'object') {
71 | url = url + (url.indexOf('?') > 0 ? '&' : '?') + queryString.stringify(options.body);
72 | options.body = null;
73 | }
74 | }
75 |
76 | return fetch(url, options).then(checkStatus).then(parseJSON);
77 | }
78 |
79 | export default function xFetch(url, options) {
80 | return new BaseFetch(url, options)
81 | }
82 |
--------------------------------------------------------------------------------
/src/views/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 计算机集成管理系统
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./build/webpack.dev.conf.js');
2 |
--------------------------------------------------------------------------------