├── .editorconfig ├── .eslintrc ├── .gitignore ├── .roadhogrc.js ├── .roadhogrc.mock.js ├── .webpackrc ├── Dockerfile ├── README.md ├── data.json ├── mock └── .gitkeep ├── package-lock.json ├── package.json ├── public └── index.html ├── routers └── hero.js ├── server └── index.js ├── src ├── assets │ ├── bg.jpg │ └── yay.jpg ├── components │ ├── DeleteConfirm.js │ ├── Example.js │ ├── Header.jsx │ └── Public.less ├── index.css ├── index.js ├── models │ └── hero.js ├── router.js ├── routes │ ├── detail │ │ ├── Detail.jsx │ │ └── Detail.less │ ├── list │ │ ├── List.jsx │ │ └── List.less │ └── login │ │ ├── Login.jsx │ │ └── Login.less ├── services │ ├── example.js │ └── hero.js └── utils │ ├── axios.js │ └── request.js ├── vmodels └── heroSchema.js └── web ├── Dockerfile ├── dist ├── index.css ├── index.html ├── index.js └── static │ └── bg.7ea45f27.jpg └── nginx └── default.conf /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 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 | "extends": "umi" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | 7 | # misc 8 | .DS_Store 9 | npm-debug.log* 10 | -------------------------------------------------------------------------------- /.roadhogrc.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | var pxtorem = require('postcss-pxtorem'); 3 | const svgSpriteDirs = [ 4 | require.resolve('antd').replace(/warn\.js$/, ''), // antd-mobile 内置svg 5 | path.resolve(__dirname, 'src/assets'), // 业务代码本地私有 svg 存放目录 6 | ]; 7 | export default { 8 | entry: "src/index.js", 9 | disableCSSModules: true, 10 | publicPath: "./", 11 | less: true, 12 | env: { 13 | development: { 14 | extraBabelPlugins: [ 15 | "dva-hmr", 16 | "transform-runtime", 17 | ["import", { "libraryName": "antd-mobile", 'libraryDirectory': 'lib', "style": true }] 18 | ], 19 | extraPostCSSPlugins: [ 20 | pxtorem({ 21 | rootValue: 100, 22 | propWhiteList: [], 23 | }), 24 | ], 25 | }, 26 | production: { 27 | extraBabelPlugins: [ 28 | "transform-runtime", 29 | ["import", { "libraryName": "antd-mobile", 'libraryDirectory': 'lib', "style": true }] 30 | ], 31 | extraPostCSSPlugins: [ 32 | pxtorem({ 33 | rootValue: 100, 34 | propWhiteList: [], 35 | }), 36 | ] 37 | } 38 | }, 39 | 40 | autoprefixer: { 41 | "browsers": [ 42 | "iOS >= 8", "Android >= 4" 43 | ] 44 | }, 45 | svgSpriteLoaderDirs: svgSpriteDirs, 46 | 47 | } 48 | -------------------------------------------------------------------------------- /.roadhogrc.mock.js: -------------------------------------------------------------------------------- 1 | 2 | export default { 3 | }; 4 | -------------------------------------------------------------------------------- /.webpackrc: -------------------------------------------------------------------------------- 1 | { 2 | "extraBabelPlugins": [ 3 | ["import", { "libraryName": "antd", "libraryDirectory": "es", "style": "css" }] 4 | ], 5 | "proxy":{ 6 | "/":{ 7 | "target":"http://localhost:10005", 8 | "changeOrigin": true 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.16.3 2 | RUN mkdir -p /home/project 3 | WORKDIR /home/project 4 | COPY . /home/project 5 | RUN npm install 6 | EXPOSE 50002 7 | CMD ["npm", "run", "server"] 8 | 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mongo-react 2 | ## preview 3 | ![](https://github.com/chengheai/review-demo-image/blob/master/2018-12-10%2014-30-31.2018-12-10%2014_31_55.gif) 4 | ![](https://github.com/chengheai/review-demo-image/blob/master/2018-12-10%2014-41-32.2018-12-10%2014_49_11.gif) 5 | ![](https://github.com/chengheai/review-demo-image/blob/master/2018-12-10%2014-51-07.2018-12-10%2014_59_38.gif) 6 | ![](https://github.com/chengheai/review-demo-image/blob/master/2018-12-10%2015-08-45.2018-12-10%2015_14_15.gif) 7 | -------------------------------------------------------------------------------- /mock/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chengheai/mongo-react/665ed3208064b65f8787d7e765dcd13588e1211c/mock/.gitkeep -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "start": "roadhog server", 5 | "build": "roadhog build", 6 | "server": "node app.js", 7 | "lint": "eslint --ext .js src test", 8 | "precommit": "npm run lint" 9 | }, 10 | "dependencies": { 11 | "antd": "^3.10.8", 12 | "axios": "^0.18.0", 13 | "babel-plugin-import": "^1.11.0", 14 | "body-parser": "^1.18.2", 15 | "dva": "^2.4.1", 16 | "express": "^4.16.2", 17 | "mongoose": "^5.0.10", 18 | "nodemon": "^1.18.4", 19 | "react": "^16.2.0", 20 | "react-dom": "^16.2.0", 21 | "require_optional": "^1.0.1" 22 | }, 23 | "devDependencies": { 24 | "babel-plugin-dva-hmr": "^0.3.2", 25 | "eslint": "^4.14.0", 26 | "eslint-config-umi": "^0.1.1", 27 | "eslint-plugin-flowtype": "^2.34.1", 28 | "eslint-plugin-import": "^2.6.0", 29 | "eslint-plugin-jsx-a11y": "^5.1.1", 30 | "eslint-plugin-react": "^7.1.0", 31 | "husky": "^0.12.0", 32 | "redbox-react": "^1.4.3", 33 | "less": "^2.7.3", 34 | "roadhog": "^2.0.0" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | LOL 英雄列表 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /routers/hero.js: -------------------------------------------------------------------------------- 1 | 2 | //引入express模块 3 | const express = require("express"); 4 | //定义路由级中间件 5 | const router = express.Router(); 6 | //引入数据模型模块 7 | const Hero = require("../vmodels/heroSchema"); 8 | 9 | // 查询所有英雄信息路由 10 | router.get("/hero", (req, res) => { 11 | // console.log('========',req.query.pageSize) 12 | // console.log('+++++++++++++',Hero.count()) 13 | var total = 0; 14 | Hero.count({}, function(err, count){ 15 | if(err) return; 16 | total = count; 17 | res.set('x-header', total) 18 | }) 19 | 20 | Hero.find({}) 21 | .limit(Math.min(parseInt(req.query.pageSize) || 10, 100)) 22 | .skip(parseInt(req.query.currentPage -1) * req.query.pageSize || 0) 23 | .sort({ updatedAt: -1 }) 24 | .then(heros => { 25 | res.json(heros); 26 | }) 27 | .catch(err => { 28 | res.json(err); 29 | }) 30 | 31 | }); 32 | 33 | // 通过ObjectId查询单个英雄信息路由 34 | router.get("/hero/:id", (req, res) => { 35 | Hero.findById(req.params.id) 36 | .then(hero => { 37 | res.json(hero); 38 | }) 39 | .catch(err => { 40 | res.json(err); 41 | }); 42 | }); 43 | 44 | // 添加一个英雄信息路由 45 | router.post("/hero", (req, res) => { 46 | //使用Hero model上的create方法储存数据 47 | Hero.create(req.body, (err, hero) => { 48 | if (err) { 49 | res.json(err); 50 | } else { 51 | res.json(hero); 52 | } 53 | }); 54 | }); 55 | 56 | //更新一条英雄信息数据路由 57 | router.put("/hero/:id", (req, res) => { 58 | Hero.findOneAndUpdate( 59 | { _id: req.params.id }, 60 | { 61 | $set: { 62 | name: req.body.name, 63 | nickname: req.body.nickname, 64 | sex: req.body.sex, 65 | address: req.body.address, 66 | dowhat: req.body.dowhat, 67 | favourite: req.body.favourite, 68 | explain: req.body.explain 69 | } 70 | }, 71 | { 72 | new: true 73 | } 74 | ) 75 | .then(hero => res.json(hero)) 76 | .catch(err => res.json(err)); 77 | }); 78 | 79 | // 添加图片路由 80 | router.put("/addpic/:id", (req, res) => { 81 | Hero.findOneAndUpdate( 82 | { _id: req.params.id }, 83 | { 84 | $push: { 85 | imgArr: req.body.url 86 | } 87 | }, 88 | { 89 | new: true 90 | } 91 | ) 92 | .then(hero => res.json(hero)) 93 | .catch(err => res.json(err)); 94 | }); 95 | // 编辑图片 96 | router.put("/editpic/:id", (req, res) => { 97 | Hero.findOneAndUpdate( 98 | { _id: req.params.id }, 99 | { 100 | $set: { 101 | imgArr: req.body.imgArr 102 | } 103 | }, 104 | { 105 | new: true 106 | } 107 | ) 108 | .then(hero => res.json(hero)) 109 | .catch(err => res.json(err)); 110 | }); 111 | 112 | //删除一条英雄信息路由 113 | router.delete("/hero/:id", (req, res) => { 114 | Hero.findOneAndRemove({ 115 | _id: req.params.id 116 | }) 117 | .then(hero => res.send(`${hero.name}删除成功`)) 118 | .catch(err => res.json(err)); 119 | }); 120 | 121 | module.exports = router; 122 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const hero = require('../routers/hero') 3 | const mongoose = require("mongoose"); 4 | const path = require('path') 5 | const fs = require('fs') 6 | const bodyParser = require("body-parser") 7 | 8 | 9 | 10 | 11 | //这一句是连接上数据库 12 | // var db = mongoose.connect('mongodb://127.0.0.1:27017/reactTest',{ useNewUrlParser: true }); 13 | // console.log('db:===',db) 14 | // # mongodb 为协议 15 | // # james: 连接数据库的用户 16 | // # 123456: 该用户的密码 17 | // # localhost: 本地的地址(因为这是本地环境) 18 | // # 27017: mongodb的端口号(这个一般是默认值,也可以进行修改) 19 | // # example: 数据库的名字 20 | // var db = 'mongodb://root:1234@192.168.56.101:27017/reactTest?authMechanism=SCRAM-SHA-1' 21 | 22 | // ...... 23 | // mongoose.connect(db,{useUnifiedTopology: true, useNewUrlParser: true}); 24 | mongoose.connect("mongodb://localhost:27017/reactTest", { 25 | useNewUrlParser: true 26 | }); 27 | 28 | 29 | const app = express() 30 | app.use(bodyParser.json()); 31 | app.use(bodyParser.urlencoded({ extended: false })); 32 | app.use('/api',hero) 33 | // app.use(express.static(path.resolve(__dirname, './dist'))) 34 | // 首页静态页面 35 | // app.get('*', function(req, res) { 36 | // const html = fs.readFileSync(path.resolve(__dirname, './dist/index.html'), 'utf-8') 37 | // res.send(html) 38 | // }) 39 | app.all('*', function(req, res, next) { 40 | res.header("Access-Control-Allow-Origin", "*"); 41 | res.header("Access-Control-Allow-Headers", "X-Requested-With,Content-Type"); 42 | res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS"); 43 | next(); 44 | }); 45 | // 监听80端口 46 | app.listen(50002); 47 | console.log('server is running 50002'); 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/assets/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chengheai/mongo-react/665ed3208064b65f8787d7e765dcd13588e1211c/src/assets/bg.jpg -------------------------------------------------------------------------------- /src/assets/yay.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chengheai/mongo-react/665ed3208064b65f8787d7e765dcd13588e1211c/src/assets/yay.jpg -------------------------------------------------------------------------------- /src/components/DeleteConfirm.js: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { Modal } from 'antd'; 4 | import styles from './Public.less'; 5 | 6 | function DeleteConfirm({ 7 | content = '确认删除吗?', 8 | ...modalProps 9 | }) { 10 | const modalOpts = { 11 | ...modalProps, 12 | width: 610, 13 | }; 14 | 15 | return ( 16 | 17 |
18 | {content} 19 |
20 |
21 | ); 22 | } 23 | 24 | export default DeleteConfirm; 25 | -------------------------------------------------------------------------------- /src/components/Example.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Example = () => { 4 | return ( 5 |
6 | Example 7 |
8 | ); 9 | }; 10 | 11 | Example.propTypes = { 12 | }; 13 | 14 | export default Example; 15 | -------------------------------------------------------------------------------- /src/components/Header.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'dva/router'; 3 | import { Icon, Layout, Menu, Dropdown } from 'antd'; 4 | const { Header } = Layout; 5 | const menu = ( 6 | 7 | 8 | 9 | 10 | logout 11 | 12 | 13 | 14 | 15 | 16 | github 17 | 18 | 19 | 20 | ); 21 | class HeaderLayout extends React.Component { 22 | constructor(props) { 23 | super(props); 24 | this.state = { 25 | username: '' 26 | }; 27 | } 28 | componentDidMount() { 29 | const username = sessionStorage.getItem('guest'); 30 | this.setState({ 31 | username 32 | }) 33 | } 34 | render() { 35 | return ( 36 |
37 |
38 |

LOGO

39 | 40 | 41 | 42 | {this.state.username} 43 | 44 | 45 |
46 |
47 | ); 48 | } 49 | }; 50 | 51 | HeaderLayout.propTypes = { 52 | }; 53 | 54 | export default HeaderLayout; 55 | -------------------------------------------------------------------------------- /src/components/Public.less: -------------------------------------------------------------------------------- 1 | .modal { 2 | margin: 15px 0 15px 35px; 3 | color: rgba(37,48,57,0.60); 4 | text-align: center; 5 | } 6 | 7 | :global(.vertical-center-modal) { 8 | text-align: center; 9 | white-space: nowrap; 10 | :global(.ant-modal) { 11 | display: inline-block; 12 | vertical-align: middle; 13 | top: 0; 14 | text-align: left; 15 | } 16 | } 17 | :global(.vertical-center-modal:before) { 18 | content: ''; 19 | display: inline-block; 20 | height: 100%; 21 | vertical-align: middle; 22 | width: 0; 23 | } 24 | 25 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | 2 | html, body, :global(#root) { 3 | height: 100%; 4 | } 5 | 6 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import dva from 'dva'; 2 | import { message } from 'antd'; 3 | import './index.css'; 4 | 5 | // 1. Initialize 6 | const app = dva({ 7 | onError(e, dispatch) { 8 | message.error(`${e.message}` || '接口错误'); 9 | }, 10 | }); 11 | 12 | // 2. Plugins 13 | // app.use({}); 14 | 15 | // 3. Model 16 | app.model(require('./models/hero').default); 17 | 18 | // 4. Router 19 | app.router(require('./router').default); 20 | 21 | // 5. Start 22 | app.start('#root'); 23 | -------------------------------------------------------------------------------- /src/models/hero.js: -------------------------------------------------------------------------------- 1 | import { get_heros, post_hero, delete_hero, put_heros, put_add_pic, get_hero_detail, put_edit_pic } from '../services/hero'; 2 | import queryString from 'query-string'; 3 | export default { 4 | namespace: 'Hero', 5 | 6 | state: { 7 | detail: {}, 8 | }, 9 | 10 | subscriptions: { 11 | setup({ dispatch, history }) { 12 | // eslint-disable-line 13 | console.log(history) 14 | history.listen((location) => { 15 | const pathname = location.pathname; 16 | let { search } = location; 17 | const query = queryString.parse(search); 18 | console.log('query: ', query); 19 | if(pathname === '/detail') { 20 | dispatch({ 21 | type: 'get_hero_detail', //在model内不需要加namespace,如果在外面用则需要加 22 | payload: query.id 23 | }) 24 | } 25 | }) 26 | }, 27 | }, 28 | 29 | effects: { 30 | *get_heros({ payload, callback }, { call }) { 31 | const data = yield call(get_heros, payload); 32 | callback(data); 33 | }, 34 | *post_hero({ payload, callback }, { call }) { 35 | const response = yield call(post_hero, payload); 36 | const data = response.data; 37 | callback(data); 38 | }, 39 | *delete_hero({ payload, callback }, { call }) { 40 | const response = yield call(delete_hero, payload); 41 | const data = response.data; 42 | callback(data); 43 | }, 44 | *put_heros({ payload, callback }, { call }) { 45 | const response = yield call(put_heros, payload); 46 | const data = response.data; 47 | callback(data); 48 | }, 49 | *put_add_pic({ payload, callback }, { call }) { 50 | const response = yield call(put_add_pic, payload); 51 | const data = response.data; 52 | callback(data); 53 | }, 54 | *put_edit_pic({ payload, callback }, { call }) { 55 | const response = yield call(put_edit_pic, payload); 56 | const data = response.data; 57 | callback(data); 58 | }, 59 | *get_hero_detail({ payload, callback }, { call, put }) { 60 | yield put({ type: 'queryHeroDetail', payload: { detail: {} } }); 61 | const data = yield call(get_hero_detail, payload); 62 | console.log('detail:', data.data) 63 | if (data) { 64 | yield put({ 65 | type: 'queryHeroDetail', 66 | payload: { 67 | detail: data.data 68 | }, 69 | }) 70 | } 71 | } 72 | }, 73 | reducers: { 74 | queryHeroDetail(state, action) { 75 | return { ...state, ...action.payload, ...action.detail }; 76 | } 77 | } 78 | }; 79 | -------------------------------------------------------------------------------- /src/router.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Router, Route, Switch } from 'dva/router'; 3 | import Login from './routes/login/Login.jsx'; 4 | import List from './routes/list/List.jsx'; 5 | import Detail from './routes/detail/Detail.jsx'; 6 | 7 | function RouterConfig({ history }) { 8 | return ( 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | } 18 | 19 | export default RouterConfig; 20 | -------------------------------------------------------------------------------- /src/routes/detail/Detail.jsx: -------------------------------------------------------------------------------- 1 | import Header from './../../components/Header.jsx'; 2 | import React from 'react'; 3 | import { connect } from 'dva'; 4 | 5 | import { Carousel, Button } from 'antd'; 6 | import { Link } from 'dva/router'; 7 | 8 | class Detail extends React.Component { 9 | constructor(props) { 10 | super(props); 11 | this.state = { 12 | // imgArr: [], 13 | // name: '', 14 | // favourite: '', 15 | // explain: '' 16 | }; 17 | } 18 | 19 | UNSAFE_componentWillMount() { 20 | // console.log(this.props) 21 | // const { location, dispatch } = this.props; 22 | // console.log('location: ', location); 23 | // console.log('dispatch: ', dispatch); 24 | // let id = location.pathname.split('/')[2]; 25 | // dispatch({ 26 | // type: 'heroModel/get_hero_detail', 27 | // payload: id, 28 | // callback: (res) => { 29 | // console.log(res); 30 | // this.setState({ 31 | // imgArr: res.imgArr, 32 | // explain: res.explain, 33 | // favourite: res.favourite, 34 | // name: res.name 35 | // }) 36 | // } 37 | // }) 38 | } 39 | render() { 40 | const { Hero } = this.props; 41 | // console.log('Hero: ', Hero); 42 | // console.log('Hero: ', Hero.detail); 43 | let imgArr, explain, favourite, name; 44 | if (Hero && Object.keys(Hero.detail).length !== 0) { 45 | imgArr = Hero.detail.imgArr; 46 | explain = Hero.detail.explain; 47 | favourite = Hero.detail.favourite; 48 | name = Hero.detail.name; 49 | } 50 | return ( 51 |
52 |
53 |
54 | 55 | 56 | 57 |
58 |
59 | 60 | { imgArr ? imgArr.map((item, index) => { 61 | return ( 62 |
63 | 图片加载失败 69 |
70 | ); 71 | }) 72 | : ''} 73 |
74 |

75 | {name} ******* {favourite} 76 |

77 |

被动:

78 |

{explain}

79 |
80 |
81 | ); 82 | } 83 | } 84 | Detail.propTypes = {}; 85 | function mapStateToProps({ Hero }) { 86 | return { Hero }; 87 | } 88 | export default connect(mapStateToProps)(Detail); 89 | -------------------------------------------------------------------------------- /src/routes/detail/Detail.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chengheai/mongo-react/665ed3208064b65f8787d7e765dcd13588e1211c/src/routes/detail/Detail.less -------------------------------------------------------------------------------- /src/routes/list/List.jsx: -------------------------------------------------------------------------------- 1 | import Header from './../../components/Header.jsx'; 2 | import React from 'react'; 3 | import { connect } from 'dva'; 4 | import { Link } from 'dva/router'; 5 | 6 | import { Table, Button, Layout, message, Modal, Drawer, Form, Col, Row, Input, Select, Tag } from 'antd'; 7 | 8 | const { Option } = Select; 9 | const { Content } = Layout; 10 | const confirm = Modal.confirm; 11 | class List extends React.Component { 12 | constructor(props) { 13 | super(props); 14 | this.state = { 15 | collapsed: false, 16 | visible: false, 17 | imgVisible: false, 18 | loading: true, 19 | tableData: [], 20 | total: 0, 21 | imgUrl: '', 22 | imgId: '', 23 | editForm: {}, 24 | pagination: { 25 | currentPage: 1, 26 | pageSize: 10, 27 | }, 28 | }; 29 | } 30 | toggleCollapsed = () => { 31 | this.setState({ 32 | collapsed: !this.state.collapsed, 33 | }); 34 | }; 35 | showDrawer = () => { 36 | this.setState({ 37 | visible: true, 38 | types: 1 39 | }); 40 | }; 41 | onClose = () => { 42 | this.setState({ 43 | visible: false, 44 | }); 45 | }; 46 | // pic 47 | showImgDrawer = (id) => { 48 | console.log(id); 49 | this.setState({ 50 | imgVisible: true, 51 | imgId: id, 52 | }) 53 | }; 54 | 55 | imgClose = () => { 56 | this.setState({ 57 | imgVisible: false, 58 | }); 59 | }; 60 | 61 | handleChange = (value) => { 62 | console.log(`Selected: ${value}`); 63 | } 64 | onShowSizeChange = (current, pageSize) => { 65 | console.log(current, pageSize); 66 | } 67 | componentDidMount() { 68 | this.getData(); 69 | } 70 | componentDidUpdate() { 71 | if(this.state.visible === false) { 72 | this.props.form.resetFields(); 73 | } 74 | } 75 | //编辑 76 | editHandle = (obj) =>{ 77 | console.log('obj: ', obj); 78 | this.setState({ 79 | visible: true, 80 | types: 2, 81 | editForm: obj 82 | }); 83 | this.props.form.setFieldsValue({ 84 | name: obj.name, 85 | nickname: obj.nickname, 86 | explain: obj.explain, 87 | favourite: obj.favourite, 88 | sex: obj.sex, 89 | dowhat: obj.dowhat, 90 | address: obj.address 91 | }); 92 | } 93 | ImputChange = (e) => { 94 | // console.log(e.target.value); 95 | this.setState({ 96 | imgUrl: e.target.value 97 | }) 98 | } 99 | // 添加图片 100 | addPicHandle = () => { 101 | let that = this; 102 | const { imgUrl, imgId } = that.state; 103 | const { dispatch } = this.props; 104 | console.log(imgUrl,imgId) 105 | if (!imgUrl.trim()) { 106 | message.info('图片格式不正确'); 107 | } else { 108 | dispatch({ 109 | type: 'Hero/put_add_pic', 110 | payload: { 111 | id: imgId, 112 | url: imgUrl 113 | }, 114 | callback: (res) => { 115 | message.success('添加成功'); 116 | this.setState({ 117 | imgUrl: '', 118 | imgId: '', 119 | imgVisible: false 120 | }) 121 | } 122 | }) 123 | } 124 | } 125 | // 添加 126 | handleSubmit = (e) => { 127 | let that = this; 128 | const { dispatch } = this.props; 129 | const { types, editForm } = this.state; 130 | e.preventDefault(); 131 | if(types === 2){ 132 | this.props.form.validateFields((err, values) => { 133 | values = Object.assign(editForm, values); 134 | console.log('values: ', values); 135 | if (!err) { 136 | // message.loading('正在添加...'); 137 | dispatch({ 138 | type: 'Hero/put_heros', 139 | payload: values, 140 | callback: (res) => { 141 | message.success('修改成功'); 142 | this.setState({ 143 | visible: false, 144 | editId: '' 145 | }); 146 | this.props.form.resetFields(); 147 | that.getData(); 148 | } 149 | }) 150 | } 151 | }); 152 | } else { 153 | this.props.form.validateFields((err, values) => { 154 | if (!err) { 155 | // message.loading('正在添加...'); 156 | dispatch({ 157 | type: 'Hero/post_hero', 158 | payload: values, 159 | callback: (res) => { 160 | message.success('添加成功'); 161 | this.setState({ 162 | visible: false, 163 | }); 164 | this.props.form.resetFields(); 165 | that.getData(); 166 | } 167 | }) 168 | } 169 | }); 170 | } 171 | } 172 | // 删除 173 | showDeleteConfirm = (id) => { 174 | let that = this; 175 | const { dispatch } = this.props; 176 | confirm({ 177 | title: '此操作将永久删除该文件, 是否继续?', 178 | okText: '确定', 179 | okType: 'primary', 180 | cancelText: '取消', 181 | onOk() { 182 | dispatch({ 183 | type: 'Hero/delete_hero', 184 | payload: id, 185 | callback: (res) => { 186 | message.success('删除成功'); 187 | that.getData(); 188 | } 189 | }) 190 | console.log(id) 191 | }, 192 | onCancel() { 193 | console.log('Cancel'); 194 | }, 195 | }); 196 | } 197 | // 获取所有 198 | getData = () => { 199 | const { dispatch } = this.props; 200 | const { pagination, payload } = this.state; 201 | const query = { 202 | ...pagination, 203 | ...payload 204 | } 205 | dispatch({ 206 | type: 'Hero/get_heros', 207 | payload: query, 208 | callback: res =>{ 209 | this.setState({ 210 | tableData: res.data, 211 | // eslint-disable-next-line 212 | total: parseInt(res.headers['x-header']) 213 | }) 214 | } 215 | }) 216 | this.setState({ 217 | loading: false 218 | }) 219 | } 220 | // 翻页 221 | handleTableChange = (pagination, filters, sorter) => { 222 | console.log(pagination); 223 | // let that = this; 224 | const { pageSize, current } = pagination; 225 | const { dispatch } = this.props; 226 | const { payload } = this.state; 227 | this.setState({ 228 | pagination: { 229 | currentPage: current, 230 | pageSize, 231 | }, 232 | }); 233 | const query = { 234 | ...payload, 235 | pageSize, 236 | currentPage: current 237 | }; 238 | dispatch({ 239 | type: 'Hero/get_heros', 240 | payload: query, 241 | callback: res => { 242 | this.setState({ 243 | tableData: res.data, 244 | // eslint-disable-next-line 245 | total: parseInt(res.headers['x-header']) 246 | }) 247 | } 248 | }); 249 | } 250 | render() { 251 | const { getFieldDecorator } = this.props.form; 252 | const { loading, tableData, total, pagination, types } = this.state; 253 | const columns = [ 254 | { 255 | title: '英雄', 256 | key: 'nickname', 257 | dataIndex: 'nickname', 258 | width: 150, 259 | }, 260 | { 261 | title: '名字', 262 | key: 'name', 263 | dataIndex: 'name', 264 | width: 100, 265 | }, 266 | { 267 | title: '性别', 268 | key: 'sex', 269 | dataIndex: 'sex', 270 | render: sex => (sex === 'man' ? '汉子' : '妹子'), 271 | width: 80, 272 | }, 273 | { 274 | title: '籍贯', 275 | key: 'address', 276 | dataIndex: 'address', 277 | render: address => (address === '0' ? '艾欧尼亚' : address === '1' ? '祖安' : address === '2' ? '雷瑟守备' : address === '3'? '裁决之地' : '黑色玫瑰'), 278 | width: 150, 279 | }, 280 | { 281 | title: '位置', 282 | key: 'dowhat', 283 | dataIndex: 'dowhat', 284 | render: dowhat => ( 285 | 286 | {dowhat.map(tag => {tag === '0' ? '上单': tag === '1'? '中单': tag === '2'? '下路': tag === '3'? '辅助': '打野' })} 287 | {/* {dowhat.map(tag => { 288 | tag === '0'? 上单 : tag === '1'? 中单 : tag === '2'? 下路 : tag === '3'? 辅助 : 打野 289 | })} */} 290 | 291 | ), 292 | width: 180, 293 | }, 294 | { 295 | title: '台词', 296 | key: 'favourite', 297 | dataIndex: 'favourite', 298 | width: 250, 299 | }, 300 | { 301 | title: '操作', 302 | width: 350, 303 | // fixed: 'right', 304 | render: (record, obj) => ( 305 | 306 | { 307 | 308 | 309 | 310 | 311 | 315 | 322 | 327 | 328 | } 329 | 330 | ), 331 | }, 332 | ]; 333 | return ( 334 |
335 | 336 |
337 | 338 |
339 | 342 |
343 | (总页数: {total}), }} /> 357 | 358 | 359 | {/* 编辑添加 */} 360 | 372 |
373 | 374 |
375 | 376 | {getFieldDecorator('name', { 377 | rules: [{ required: true, message: '请输入名字' }], 378 | })()} 379 | 380 | 381 | 382 | 383 | {getFieldDecorator('nickname', { 384 | rules: [{ required: true, message: '请输入英雄名称' }], 385 | })()} 386 | 387 | 388 | 389 | 390 | 391 | 392 | {getFieldDecorator('sex', { 393 | rules: [{ required: true, message: '请选择性别' }], 394 | })( 395 | 399 | )} 400 | 401 | 402 | 403 | 404 | {getFieldDecorator('address', { 405 | rules: [{ required: true, message: '请选择籍贯' }], 406 | })( 407 | 414 | )} 415 | 416 | 417 | 418 | 419 | 420 | 421 | {getFieldDecorator('dowhat', { 422 | rules: [{ required: true, message: '请选择位置' }], 423 | })( 424 | 436 | )} 437 | 438 | 439 | 440 | 441 | {getFieldDecorator('favourite', { 442 | rules: [{ required: true, message: '请输入台词' }], 443 | })()} 444 | 445 | 446 | 447 | 448 | 449 | 450 | {getFieldDecorator('explain', { 451 | rules: [ 452 | { 453 | required: true, 454 | message: '英雄故事', 455 | }, 456 | ], 457 | })()} 458 | 459 | 460 | 461 | 462 |
475 | 483 | 487 |
488 | 489 | {/* 添加图片 */} 490 | 497 | 498 |
499 | {this.state.imgList} 500 |
501 |
514 | 522 | 525 |
526 |
527 | 528 | ); 529 | } 530 | } 531 | const ListInfo = Form.create()(List); 532 | List.propTypes = {}; 533 | function mapStateToProps({ Hero }) { 534 | return { Hero }; 535 | } 536 | export default connect(mapStateToProps)(ListInfo); 537 | -------------------------------------------------------------------------------- /src/routes/list/List.less: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chengheai/mongo-react/665ed3208064b65f8787d7e765dcd13588e1211c/src/routes/list/List.less -------------------------------------------------------------------------------- /src/routes/login/Login.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'dva'; 3 | import { Form, Icon, Input, Button } from 'antd'; 4 | import { Link, routerRedux } from 'dva/router'; 5 | import './Login.less'; 6 | import bg from '../../assets/bg.jpg'; 7 | const FormItem = Form.Item; 8 | 9 | class NormalLoginForm extends React.Component { 10 | state = { 11 | userName: 'guest', 12 | password: 'guest' 13 | } 14 | handleSubmit = (e) => { 15 | const { dispatch } = this.props; 16 | e.preventDefault(); 17 | this.props.form.validateFields((err, values) => { 18 | if (!err) { 19 | dispatch(routerRedux.replace('/list')); 20 | console.log('Received values of form: ', values); 21 | sessionStorage.setItem('guest', values.userName) 22 | } 23 | }); 24 | } 25 | render() { 26 | const { getFieldDecorator } = this.props.form; 27 | const { userName, password } = this.state; 28 | const sty = { 29 | backgroundImage: `url(${bg})`, 30 | backgroundSize: 'cover', 31 | height: '100%', 32 | width: '100%', 33 | position: 'relative' 34 | } 35 | return ( 36 |
37 |
38 | 39 | {getFieldDecorator('userName', { 40 | rules: [{ required: true, message: 'Please input your username!' }], 41 | initialValue: userName 42 | })( 43 | } placeholder="Username" /> 44 | )} 45 | 46 | 47 | {getFieldDecorator('password', { 48 | rules: [{ required: true, message: 'Please input your Password!' }], 49 | initialValue: password 50 | })( 51 | } type="password" placeholder="Password" /> 52 | )} 53 | 54 | 55 | 58 | 59 | 60 |
61 | ); 62 | } 63 | } 64 | 65 | const Login = Form.create()(NormalLoginForm); 66 | 67 | export default connect()(Login); 68 | -------------------------------------------------------------------------------- /src/routes/login/Login.less: -------------------------------------------------------------------------------- 1 | .login-form{ 2 | background: red; 3 | } 4 | -------------------------------------------------------------------------------- /src/services/example.js: -------------------------------------------------------------------------------- 1 | import axios from '../utils/axios' 2 | 3 | // 获取英雄列表 4 | export async function get_heros(params) { 5 | return axios.get('/api/hero', { params }); 6 | } 7 | 8 | // 编辑英雄信息 9 | export async function put_heros(params) { 10 | return axios.put(`/api/hero/${params._id}`, params); 11 | } 12 | 13 | // 添加新英雄 14 | export async function post_hero(params) { 15 | return axios.post(`/api/hero`, params); 16 | } 17 | 18 | // 删除英雄 19 | export async function delete_hero(params) { 20 | return axios.delete(`/api/hero/${params}`, params) 21 | } 22 | 23 | // 英雄添加图片 24 | export async function put_add_pic(params) { 25 | return axios.put(`/api/addpic/${params.id}`, params) 26 | } 27 | 28 | // 获取单个英雄详情 29 | export async function get_hero_detail(params) { 30 | return axios.get(`/api/hero/${params}`, params) 31 | } 32 | -------------------------------------------------------------------------------- /src/services/hero.js: -------------------------------------------------------------------------------- 1 | import axios from '../utils/axios' 2 | 3 | // 获取英雄列表 4 | export async function get_heros(params) { 5 | return axios.get('/api/hero', { params }); 6 | } 7 | 8 | // 编辑英雄信息 9 | export async function put_heros(params) { 10 | return axios.put(`/api/hero/${params._id}`, params); 11 | } 12 | 13 | // 添加新英雄 14 | export async function post_hero(params) { 15 | return axios.post(`/api/hero`, params); 16 | } 17 | 18 | // 删除英雄 19 | export async function delete_hero(params) { 20 | return axios.delete(`/api/hero/${params}`, params) 21 | } 22 | 23 | // 英雄添加图片 24 | export async function put_add_pic(params) { 25 | return axios.put(`/api/addpic/${params.id}`, params) 26 | } 27 | // 英雄b编辑图片 28 | export async function put_edit_pic(params) { 29 | return axios.put(`/api/editpic/${params.id}`, params) 30 | } 31 | 32 | // 获取单个英雄详情 33 | export async function get_hero_detail(params) { 34 | return axios.get(`/api/hero/${params}`, params) 35 | } 36 | -------------------------------------------------------------------------------- /src/utils/axios.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | // axios 配置 4 | var instance = axios.create({ 5 | // 本地测试地址: 6 | // baseURL: 'http://localhost:8000', 7 | // 线上地址: 8 | // baseURL: 'http://144.34.148.126:3000', 9 | timeout: 5000 10 | }) 11 | 12 | // 可以在这先申明错误代码表示的含义 13 | 14 | // 添加请求拦截器 15 | instance.interceptors.request.use(config => { 16 | // 在发送请求之前做些什么 17 | // config.data = JSON.stringify(config.data) 18 | config.headers = { 19 | 'Content-Type': 'application/json' 20 | } 21 | return config 22 | }, error => { 23 | // 对请求错误做些什么 24 | console.log(error) // for debug 25 | return Promise.reject(error) 26 | }) 27 | 28 | // 添加响应拦截器 29 | instance.interceptors.response.use(response => { 30 | // 对响应数据做点什么 31 | // const res = response.data 32 | // 对错误代码做处理 33 | return response 34 | }, error => { 35 | // 对响应错误做点什么 36 | console.log('err' + error) // for debug 37 | return Promise.reject(error) 38 | }) 39 | 40 | export default instance 41 | 42 | /** 43 | * post 请求方法 44 | * @param url 45 | * @param data 46 | * @returns {Promise} 47 | */ 48 | export function post (url, data = {}) { 49 | return new Promise((resolve, reject) => { 50 | instance.post(url, data).then(response => { 51 | resolve(response.data) 52 | }, err => { 53 | reject(err) 54 | }) 55 | }) 56 | } 57 | -------------------------------------------------------------------------------- /src/utils/request.js: -------------------------------------------------------------------------------- 1 | import fetch from 'dva/fetch'; 2 | 3 | function parseJSON(response) { 4 | return response.json(); 5 | } 6 | 7 | function checkStatus(response) { 8 | if (response.status >= 200 && response.status < 300) { 9 | return response; 10 | } 11 | 12 | const error = new Error(response.statusText); 13 | error.response = response; 14 | throw error; 15 | } 16 | 17 | /** 18 | * Requests a URL, returning a promise. 19 | * 20 | * @param {string} url The URL we want to request 21 | * @param {object} [options] The options we want to pass to "fetch" 22 | * @return {object} An object containing either "data" or "err" 23 | */ 24 | export default function request(url, options) { 25 | return fetch(url, options) 26 | .then(checkStatus) 27 | .then(parseJSON) 28 | .then(data => ({ data })) 29 | .catch(err => ({ err })); 30 | } 31 | -------------------------------------------------------------------------------- /vmodels/heroSchema.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose') 2 | 3 | const heroSchema = mongoose.Schema({ 4 | name :String, 5 | nickname : String, 6 | sex : String, 7 | address : String, 8 | dowhat : [], 9 | imgArr:[], 10 | favourite:String, 11 | explain:String, 12 | createdAt : { type : Date, default : Date.now }, 13 | }, { collection: 'myhero', timestamps: true}) 14 | //这里mongoose.Schema要写上第二个参数,明确指定到数据库中的哪个表取数据 15 | 16 | 17 | const Hero = module.exports = mongoose.model('hero',heroSchema); 18 | -------------------------------------------------------------------------------- /web/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx 2 | COPY dist/ /usr/share/nginx/html/ 3 | COPY nginx/default.conf /etc/nginx/conf.d/default.conf 4 | -------------------------------------------------------------------------------- /web/dist/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | LOL 英雄列表 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /web/dist/static/bg.7ea45f27.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chengheai/mongo-react/665ed3208064b65f8787d7e765dcd13588e1211c/web/dist/static/bg.7ea45f27.jpg -------------------------------------------------------------------------------- /web/nginx/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 10001; 3 | server_name localhost; 4 | 5 | #charset koi8-r; 6 | # access_log /var/log/nginx/host.access.log main; 7 | # error_log /var/log/nginx/error.log error; 8 | 9 | location / { 10 | root /usr/share/nginx/html; 11 | index index.html index.htm; 12 | } 13 | 14 | location /api { 15 | proxy_pass http://192.168.56.101:50002; 16 | index index.html; # 设置默认网页 17 | } 18 | 19 | #error_page 404 /404.html; 20 | 21 | # redirect server error pages to the static page /50x.html 22 | # 23 | error_page 500 502 503 504 /50x.html; 24 | location = /50x.html { 25 | root /usr/share/nginx/html; 26 | } 27 | } 28 | --------------------------------------------------------------------------------