Full Code of awaw00/react-curd for AI

master 7d4c1bca55e5 cached
32 files
30.6 KB
8.6k tokens
75 symbols
1 requests
Download .txt
Repository: awaw00/react-curd
Branch: master
Commit: 7d4c1bca55e5
Files: 32
Total size: 30.6 KB

Directory structure:
gitextract_ra15fb77/

├── .gitignore
├── .roadhogrc
├── README.md
├── package.json
├── public/
│   └── index.html
├── server/
│   ├── auth.js
│   ├── db.json
│   └── index.js
└── src/
    ├── components/
    │   ├── AutoComplete.js
    │   ├── BookEditor.js
    │   ├── FormItem.js
    │   └── UserEditor.js
    ├── createStore.js
    ├── index.js
    ├── layouts/
    │   └── HomeLayout.js
    ├── pages/
    │   ├── BookAdd.js
    │   ├── BookEdit.js
    │   ├── BookList.js
    │   ├── Home.js
    │   ├── Login.js
    │   ├── UserAdd.js
    │   ├── UserEdit.js
    │   └── UserList.js
    ├── reducers/
    │   ├── index.js
    │   └── user.js
    ├── styles/
    │   ├── auto-complete.less
    │   ├── home-layout.less
    │   ├── home-page.less
    │   └── login-page.less
    ├── type.js
    └── utils/
        ├── formProvider.js
        └── request.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
.idea
.vscode
node_modules
dist


================================================
FILE: .roadhogrc
================================================
{
  "extraBabelPlugins": [
    ["import", {
      "libraryName": "antd",
      "libraryDirectory": "lib",
      "style": "css"
    }]
  ]
}


================================================
FILE: README.md
================================================
# React全家桶入门系列文章项目

本教程涵盖React主流技术栈:

- react - [github](https://github.com/facebook/react)
- react-router - [github](https://github.com/ReactTraining/react-router)
- redux - [github](https://github.com/reactjs/redux)
- react-redux - [github](https://github.com/reactjs/react-redux)
- react-router-redux - [github](https://github.com/reactjs/react-router-redux)
- redux-saga - [github](https://github.com/redux-saga/redux-saga)
- immutable - [github](https://github.com/facebook/immutable-js)
- reselect - [github](https://github.com/reactjs/reselect)
- antd - [github](https://github.com/ant-design/ant-design)

请搭配[博客文章](http://blog.csdn.net/awaw00/article/category/6692955)食用。

每篇文章的代码都打了相应的tag,如:

[【React全家桶入门之二】项目搭建](http://blog.csdn.net/awaw00/article/details/54693780)的代码可以通过`git checkout -b 02 C02`来获取。

其中C02就是第二篇的tag(由于第一篇为引言没有代码,为了tag与文章对应,tag从02开始)。

*使用注意:由于一开始提交代码没有注意,C04的代码包括了【之五】的一部分代码(高阶组件),C05仅有【之五】剩余一部分的代码(组件化表单控件)。*


================================================
FILE: package.json
================================================
{
  "name": "react-curd",
  "version": "1.0.0",
  "description": "",
  "main": "src/index.js",
  "scripts": {
    "server": "node server/index.js",
    "dev": "roadhog server"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "antd": "^2.7.0",
    "react": "^15.4.2",
    "react-dom": "^15.4.2",
    "react-redux": "^5.0.3",
    "react-router": "^3.0.2",
    "redux": "^3.6.0",
    "redux-thunk": "^2.2.0"
  },
  "devDependencies": {
    "babel-plugin-import": "^1.1.0",
    "json-server": "^0.9.4"
  }
}


================================================
FILE: public/index.html
================================================
<!doctype html>
<html lang="zh-cn">
<head>
  <meta charset="UTF-8">
  <meta name="viewport"
        content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Hello React</title>
</head>
<body>
  <div id="app"></div>
  <script src="./index.js"></script>
</body>
</html>


================================================
FILE: server/auth.js
================================================
const expireTime = 1000 * 60;

module.exports = function (req, res, next) {
  res.header('Access-Control-Expose-Headers', 'access-token');
  const now = Date.now();

  let unauthorized = true;
  const token = req.headers['access-token'];
  if (token) {
    const expired = now - token > expireTime;
    if (!expired) {
      unauthorized = false;
      res.header('access-token', now);
    }
  }

  if (unauthorized) {
    res.sendStatus(401);
  } else {
    next();
  }
};


================================================
FILE: server/db.json
================================================
{
  "user": [
    {
      "id": 10000,
      "name": "一韬",
      "age": 25,
      "gender": "male"
    },
    {
      "id": 10001,
      "name": "张三",
      "age": 30,
      "gender": "female"
    }
  ],
  "book": [
    {
      "name": "前端从入门到精通1",
      "price": 9300,
      "owner_id": 10000,
      "id": 10000
    },
    {
      "id": 10001,
      "name": "Java从入门到放弃",
      "price": 1990,
      "owner_id": 10001
    }
  ]
}

================================================
FILE: server/index.js
================================================
const path = require('path');
const jsonServer = require('json-server');
const server = jsonServer.create();
const router = jsonServer.router(path.join(__dirname, 'db.json'));
const middlewares = jsonServer.defaults();

server.use(jsonServer.bodyParser);
server.use(middlewares);

server.post('/login', function (req, res, next) {
  res.header('Access-Control-Expose-Headers', 'access-token');
  const {account, password} = req.body;
  if (account === 'admin' && password === '123456') {
    res.header('access-token', Date.now());
    res.json(true);
  } else {
    res.json(false);
  }
});

server.use(require('./auth'));
server.use(router);

server.listen(3000, function () {
  console.log('JSON Server is running in http://localhost:3000');
});


================================================
FILE: src/components/AutoComplete.js
================================================
import React, { PropTypes } from 'react';
import { Input } from 'antd';
import style from '../styles/auto-complete.less';

function getItemValue (item) {
  return item.value || item;
}

class AutoComplete extends React.Component {
  constructor (props) {
    super(props);

    this.state = {
      show: false,
      displayValue: '',
      activeItemIndex: -1
    };

    this.handleKeyDown = this.handleKeyDown.bind(this);
    this.handleLeave = this.handleLeave.bind(this);
  }

  handleChange (value) {
    this.setState({activeItemIndex: -1, displayValue: ''});
    this.props.onChange(value);
  }

  handleKeyDown (e) {
    const {activeItemIndex} = this.state;
    const {options} = this.props;

    switch (e.keyCode) {
      case 13: {
        if (activeItemIndex >= 0) {
          e.preventDefault();
          e.stopPropagation();
          this.handleChange(getItemValue(options[activeItemIndex]));
        }
        break;
      }
      case 38:
      case 40: {
        e.preventDefault();
        this.moveItem(e.keyCode === 38 ? 'up' : 'down');
        break;
      }
    }
  }

  moveItem (direction) {
    const {activeItemIndex} = this.state;
    const {options} = this.props;
    const lastIndex = options.length - 1;
    let newIndex = -1;

    if (direction === 'up') {
      if (activeItemIndex === -1) {
        newIndex = lastIndex;
      } else {
        newIndex = activeItemIndex - 1;
      }
    } else {
      if (activeItemIndex < lastIndex) {
        newIndex = activeItemIndex + 1;
      }
    }

    let newDisplayValue = '';
    if (newIndex >= 0) {
      newDisplayValue = getItemValue(options[newIndex]);
    }

    this.setState({
      displayValue: newDisplayValue,
      activeItemIndex: newIndex
    });
  }

  handleEnter (index) {
    const currentItem = this.props.options[index];
    this.setState({activeItemIndex: index, displayValue: getItemValue(currentItem)});
  }

  handleLeave () {
    this.setState({activeItemIndex: -1, displayValue: ''});
  }

  render () {
    const {show, displayValue, activeItemIndex} = this.state;
    const {value, options} = this.props;
    return (
      <div className={style.wrapper}>
        <Input
          value={displayValue || value}
          onChange={e => this.handleChange(e.target.value)}
          onKeyDown={this.handleKeyDown}
          onFocus={() => this.setState({show: true})}
          onBlur={() => this.setState({show: false})}
        />
        {show && options.length > 0 && (
          <ul className={style.options} onMouseLeave={this.handleLeave}>
            {
              options.map((item, index) => {
                return (
                  <li
                    key={index}
                    className={index === activeItemIndex ? style.active : ''}
                    onMouseEnter={() => this.handleEnter(index)}
                    onClick={() => this.handleChange(getItemValue(item))}
                  >
                    {item.text || item}
                  </li>
                );
              })
            }
          </ul>
        )}
      </div>
    );
  }
}

AutoComplete.propTypes = {
  value: PropTypes.any,
  options: PropTypes.array,
  onChange: PropTypes.func
};

export default AutoComplete;


================================================
FILE: src/components/BookEditor.js
================================================
import React from 'react';
import { Input, InputNumber, Form, Button, message } from 'antd';
import AutoComplete from '../components/AutoComplete';
import request, { get } from '../utils/request';

const Option = AutoComplete.Option;
const FormItem = Form.Item;
const formLayout = {
  labelCol: {
    span: 4
  },
  wrapperCol: {
    span: 16
  }
};

class BookEditor extends React.Component {
  constructor (props) {
    super(props);
    this.state = {
      recommendUsers: []
    };
    this.handleSubmit = this.handleSubmit.bind(this);
    this.handleOwnerIdChange = this.handleOwnerIdChange.bind(this);
  }

  componentDidMount () {
    // 在componentWillMount里使用form.setFieldsValue无法设置表单的值
    // 所以在componentDidMount里进行赋值
    // see: https://github.com/ant-design/ant-design/issues/4802
    const {editTarget, form} = this.props;
    if (editTarget) {
      form.setFieldsValue(editTarget);
    }
  }

  handleSubmit (e) {
    e.preventDefault();

    const {form, editTarget} = this.props;

    form.validateFields((err, values) => {
      if (err) {
        message.warn(err);
        return;
      }

      let editType = '添加';
      let apiUrl = 'http://localhost:3000/book';
      let method = 'post';
      if (editTarget) {
        editType = '编辑';
        apiUrl += '/' + editTarget.id;
        method = 'put';
      }

      request(method, apiUrl, values)
        .then((res) => {
          if (res.id) {
            message.success(editType + '书本成功');
            this.context.router.push('/book/list');
          } else {
            message.error(editType + '失败');
          }
        })
        .catch((err) => console.error(err));
    });
  }

  getRecommendUsers (partialUserId) {
    get('http://localhost:3000/user?id_like=' + partialUserId)
      .then((res) => {
        if (res.length === 1 && res[0].id === partialUserId) {
          return;
        }

        this.setState({
          recommendUsers: res.map((user) => {
            return {
              text: `${user.id}(${user.name})`,
              value: user.id
            };
          })
        });
      });
  }

  timer = 0;

  handleOwnerIdChange (value) {
    this.setState({recommendUsers: []});

    if (this.timer) {
      clearTimeout(this.timer);
    }

    if (value) {
      this.timer = setTimeout(() => {
        this.getRecommendUsers(value);
        this.timer = 0;
      }, 200);
    }
  }

  render () {
    const {recommendUsers} = this.state;
    const {form} = this.props;
    const {getFieldDecorator} = form;
    return (
      <Form onSubmit={this.handleSubmit} style={{width: '400px'}}>
        <FormItem label="书名:" {...formLayout}>
          {getFieldDecorator('name', {
            rules: [
              {
                required: true,
                message: '请输入书名'
              }
            ]
          })(<Input type="text"/>)}
        </FormItem>

        <FormItem label="价格:" {...formLayout}>
          {getFieldDecorator('price', {
            rules: [
              {
                required: true,
                message: '请输入价格',
                type: 'number'
              },
              {
                min: 1,
                max: 99999,
                type: 'number',
                message: '请输入1~99999的数字'
              }
            ]
          })(<InputNumber/>)}
        </FormItem>

        <FormItem label="所有者:" {...formLayout}>
          {getFieldDecorator('owner_id', {
            rules: [
              {
                required: true,
                message: '请输入所有者ID'
              },
              {
                pattern: /^\d*$/,
                message: '请输入正确的ID'
              }
            ]
          })(
            <AutoComplete
              options={recommendUsers}
              onChange={this.handleOwnerIdChange}
            />
          )}
        </FormItem>
        <FormItem wrapperCol={{span: formLayout.wrapperCol.span, offset: formLayout.labelCol.span}}>
          <Button type="primary" htmlType="submit">提交</Button>
        </FormItem>
      </Form>
    );
  }
}

BookEditor.contextTypes = {
  router: React.PropTypes.object.isRequired
};

BookEditor = Form.create()(BookEditor);

export default BookEditor;


================================================
FILE: src/components/FormItem.js
================================================
import React from 'react';

class FormItem extends React.Component {
  render () {
    const {label, children, valid, error} = this.props;
    return (
      <div>
        <label>{label}</label>
        {children}
        {!valid && <span>{error}</span>}
      </div>
    );
  }
}

export default FormItem;


================================================
FILE: src/components/UserEditor.js
================================================
import React from 'react';
import { Form, Input, InputNumber, Select, Button, message } from 'antd';
import request from '../utils/request';

const FormItem = Form.Item;

const formLayout = {
  labelCol: {
    span: 4
  },
  wrapperCol: {
    span: 16
  }
};

class UserEditor extends React.Component {
  componentDidMount () {
    // 在componentWillMount里使用form.setFieldsValue无法设置表单的值
    // 所以在componentDidMount里进行赋值
    // see: https://github.com/ant-design/ant-design/issues/4802
    const {editTarget, form} = this.props;
    if (editTarget) {
      form.setFieldsValue(editTarget);
    }
  }

  handleSubmit (e) {
    e.preventDefault();

    const {form, editTarget} = this.props;

    form.validateFields((err, values) => {
      if (!err) {
        let editType = '添加';
        let apiUrl = 'http://localhost:3000/user';
        let method = 'post';
        if (editTarget) {
          editType = '编辑';
          apiUrl += '/' + editTarget.id;
          method = 'put';
        }

        request(method, apiUrl, values)
          .then((res) => {
            if (res.id) {
              message.success(editType + '用户成功');
              this.context.router.push('/user/list');
            } else {
              message.error(editType + '失败');
            }
          })
          .catch((err) => console.error(err));

      } else {
        message.warn(err);
      }
    });
  }

  render () {
    const {form} = this.props;
    const {getFieldDecorator} = form;
    return (
      <div style={{width: '400px'}}>
        <Form onSubmit={(e) => this.handleSubmit(e)}>
          <FormItem label="用户名:" {...formLayout}>
            {getFieldDecorator('name', {
              rules: [
                {
                  required: true,
                  message: '请输入用户名'
                },
                {
                  pattern: /^.{1,4}$/,
                  message: '用户名最多4个字符'
                }
              ]
            })(
              <Input type="text"/>
            )}
          </FormItem>
          <FormItem label="年龄:" {...formLayout}>
            {getFieldDecorator('age', {
              rules: [
                {
                  required: true,
                  message: '请输入年龄',
                  type: 'number'
                },
                {
                  min: 1,
                  max: 100,
                  message: '请输入1~100的年龄',
                  type: 'number'
                }
              ]
            })(
              <InputNumber/>
            )}
          </FormItem>
          <FormItem label="性别:" {...formLayout}>
            {getFieldDecorator('gender', {
              rules: [
                {
                  required: true,
                  message: '请选择性别'
                }
              ]
            })(
              <Select placeholder="请选择">
                <Select.Option value="male">男</Select.Option>
                <Select.Option value="female">女</Select.Option>
              </Select>
            )}
          </FormItem>
          <FormItem wrapperCol={{...formLayout.wrapperCol, offset: formLayout.labelCol.span}}>
            <Button type="primary" htmlType="submit">提交</Button>
          </FormItem>
        </Form>
      </div>
    );
  }
}

UserEditor.contextTypes = {
  router: React.PropTypes.object.isRequired
};

UserEditor = Form.create()(UserEditor);

export default UserEditor;


================================================
FILE: src/createStore.js
================================================
import { combineReducers } from 'redux';

export default function (initialState) {

}


================================================
FILE: src/index.js
================================================
import React from 'react';
import ReactDOM from 'react-dom';
import { Router, Route, hashHistory } from 'react-router';
import HomePage from './pages/Home';
import UserAddPage from './pages/UserAdd';
import UserListPage from './pages/UserList';
import UserEditPage from './pages/UserEdit';
import BookAddPage from './pages/BookAdd';
import BookListPage from './pages/BookList';
import BookEditPage from './pages/BookEdit';
import LoginPage from './pages/Login';
import HomeLayout from './layouts/HomeLayout';

ReactDOM.render((
  <Router history={hashHistory}>
    <Route component={HomeLayout}>
      <Route path="/" component={HomePage}/>
      <Route path="/user/add" component={UserAddPage}/>
      <Route path="/user/list" component={UserListPage}/>
      <Route path="/user/edit/:id" component={UserEditPage}/>
      <Route path="/book/add" component={BookAddPage}/>
      <Route path="/book/list" component={BookListPage}/>
      <Route path="/book/edit/:id" component={BookEditPage}/>
    </Route>
    <Route path="/login" component={LoginPage}/>
  </Router>
), document.getElementById('app'));


================================================
FILE: src/layouts/HomeLayout.js
================================================
import React from 'react';
import { Link } from 'react-router';
import { Menu, Icon } from 'antd';
import style from '../styles/home-layout.less';

const SubMenu = Menu.SubMenu;
const MenuItem = Menu.Item;

class HomeLayout extends React.Component {
  render () {
    const {children} = this.props;
    return (
      <div>
        <header className={style.header}>
          <Link to="/">ReactManager</Link>
        </header>

        <main className={style.main}>
          <div className={style.menu}>
            <Menu mode="inline" theme="dark" style={{width: '240px'}}>
              <SubMenu key="user" title={<span><Icon type="user"/><span>用户管理</span></span>}>
                <MenuItem key="user-list">
                  <Link to="/user/list">用户列表</Link>
                </MenuItem>
                <MenuItem key="user-add">
                  <Link to="/user/add">添加用户</Link>
                </MenuItem>
              </SubMenu>

              <SubMenu key="book" title={<span><Icon type="book"/><span>图书管理</span></span>}>
                <MenuItem key="book-list">
                  <Link to="/book/list">图书列表</Link>
                </MenuItem>
                <MenuItem key="book-add">
                  <Link to="/book/add">添加图书</Link>
                </MenuItem>
              </SubMenu>
            </Menu>
          </div>

          <div className={style.content}>
            {children}
          </div>
        </main>
      </div>
    );
  }
}

export default HomeLayout;


================================================
FILE: src/pages/BookAdd.js
================================================
import React from 'react';
import BookEditor from '../components/BookEditor';

class BookAdd extends React.Component {
  render () {
    return (
      <BookEditor/>
    );
  }
}

export default BookAdd;


================================================
FILE: src/pages/BookEdit.js
================================================
import React from 'react';
import BookEditor from '../components/BookEditor';
import { get } from '../utils/request';

class BookEdit extends React.Component {
  constructor (props) {
    super(props);
    this.state = {
      book: null
    };
  }

  componentWillMount () {
    const bookId = this.context.router.params.id;
    get('http://localhost:3000/book/' + bookId)
      .then(res => {
        this.setState({
          book: res
        });
      });
  }

  render () {
    const {book} = this.state;
    return book ? <BookEditor editTarget={book}/> : <span>加载中...</span>;
  }
}

BookEdit.contextTypes = {
  router: React.PropTypes.object.isRequired
};

export default BookEdit;


================================================
FILE: src/pages/BookList.js
================================================
import React from 'react';
import { message, Table, Button, Popconfirm } from 'antd';
import { get, del } from '../utils/request';

class BookList extends React.Component {
  constructor (props) {
    super(props);
    this.state = {
      bookList: []
    };
  }

  componentWillMount () {
    get('http://localhost:3000/book')
      .then(res => {
        this.setState({
          bookList: res
        });
      });
  }

  handleEdit (book) {
    this.context.router.push('/book/edit/' + book.id);
  }

  handleDel (book) {
    del('http://localhost:3000/book/' + book.id)
      .then(res => {
        this.setState({
          bookList: this.state.bookList.filter(item => item.id !== book.id)
        });
        message.success('删除图书成功');
      })
      .catch(err => {
        console.error(err);
        message.error('删除图书失败');
      });
  }

  render () {
    const {bookList} = this.state;

    const columns = [
      {
        title: '图书ID',
        dataIndex: 'id'
      },
      {
        title: '书名',
        dataIndex: 'name'
      },
      {
        title: '价格',
        dataIndex: 'price',
        render: (text, record) => <span>&yen;{record.price / 100}</span>
      },
      {
        title: '所有者ID',
        dataIndex: 'owner_id'
      },
      {
        title: '操作',
        render: (text, record) => (
          <Button.Group type="ghost">
            <Button size="small" onClick={() => this.handleEdit(record)}>编辑</Button>
            <Popconfirm title="确定要删除吗?" onConfirm={() => this.handleDel(record)}>
              <Button size="small">删除</Button>
            </Popconfirm>
          </Button.Group>
        )
      }
    ];

    return (
      <Table columns={columns} dataSource={bookList} rowKey={row => row.id}/>
    );
  }
}

BookList.contextTypes = {
  router: React.PropTypes.object.isRequired
};

export default BookList;

================================================
FILE: src/pages/Home.js
================================================
import React from 'react';
import style from '../styles/home-page.less';

class Home extends React.Component {
  render () {
    return (
      <div className={style.welcome}>
        Welcome
      </div>
    );
  }
}

export default Home;


================================================
FILE: src/pages/Login.js
================================================
import React from 'react';
import { Icon, Form, Input, Button, message } from 'antd';
import { post } from '../utils/request';
import style from '../styles/login-page.less';

const FormItem = Form.Item;

class Login extends React.Component {
  constructor () {
    super();
    this.handleSubmit = this.handleSubmit.bind(this);
  }

  handleSubmit (e) {
    e.preventDefault();

    this.props.form.validateFields((err, values) => {
      if (!err) {
        post('http://localhost:3000/login', values)
          .then((res) => {
            if (res) {
              message.info('登录成功');
              this.context.router.push('/');
            } else {
              message.info('登录失败,账号或密码错误');
            }
          });
      }
    });
  }

  render () {
    const {form} = this.props;
    const {getFieldDecorator} = form;
    return (
      <div className={style.wrapper}>

        <div className={style.body}>
          <header className={style.header}>
            ReactManager
          </header>

          <section className={style.form}>

            <Form onSubmit={this.handleSubmit}>
              <FormItem>
                {getFieldDecorator('account', {
                  rules: [
                    {
                      required: true,
                      message: '请输入管理员账号',
                      type: 'string'
                    }
                  ]
                })(
                  <Input type="text" addonBefore={<Icon type="user"/>}/>
                )}
              </FormItem>

              <FormItem>
                {getFieldDecorator('password', {
                  rules: [
                    {
                      required: true,
                      message: '请输入密码',
                      type: 'string'
                    }
                  ]
                })(
                  <Input type="password" addonBefore={<Icon type="lock"/>}/>
                )}
              </FormItem>

              <Button className={style.btn} type="primary" htmlType="submit">Sign In</Button>
            </Form>

          </section>

        </div>

      </div>
    );
  }
}

Login.contextTypes = {
  router: React.PropTypes.object.isRequired
};

Login = Form.create()(Login);

export default Login;


================================================
FILE: src/pages/UserAdd.js
================================================
import React from 'react';
import UserEditor from '../components/UserEditor';

class UserAdd extends React.Component {
  render () {
    return (
      <UserEditor/>
    );
  }
}

export default UserAdd;


================================================
FILE: src/pages/UserEdit.js
================================================
import React from 'react';
import UserEditor from '../components/UserEditor';
import { get } from '../utils/request';

class UserEdit extends React.Component {
  constructor (props) {
    super(props);
    this.state = {
      user: null
    };
  }

  componentWillMount () {
    const userId = this.context.router.params.id;
    get('http://localhost:3000/user/' + userId)
      .then(res => {
        this.setState({
          user: res
        });
      });
  }

  render () {
    const {user} = this.state;
    return user ? <UserEditor editTarget={user}/> : <span>加载中...</span>;
  }
}

UserEdit.contextTypes = {
  router: React.PropTypes.object.isRequired
};

export default UserEdit;


================================================
FILE: src/pages/UserList.js
================================================
import React from 'react';
import { message, Table, Button, Popconfirm } from 'antd';
import { get, del } from '../utils/request';

class UserList extends React.Component {
  constructor (props) {
    super(props);
    this.state = {
      userList: []
    };
  }

  componentWillMount () {
    get('http://localhost:3000/user')
      .then(res => {
        this.setState({
          userList: res
        });
      });
  }

  handleEdit (user) {
    this.context.router.push('/user/edit/' + user.id);
  }

  handleDel (user) {
    del('http://localhost:3000/user/' + user.id)
      .then(res => {
        this.setState({
          userList: this.state.userList.filter(item => item.id !== user.id)
        });
        message.success('删除用户成功');
      })
      .catch(err => {
        console.error(err);
        message.error('删除用户失败');
      });
  }

  render () {
    const {userList} = this.state;

    const columns = [
      {
        title: '用户ID',
        dataIndex: 'id'
      },
      {
        title: '用户名',
        dataIndex: 'name'
      },
      {
        title: '性别',
        dataIndex: 'gender'
      },
      {
        title: '年龄',
        dataIndex: 'age'
      },
      {
        title: '操作',
        render: (text, record) => {
          return (
            <Button.Group type="ghost">
              <Button size="small" onClick={() => this.handleEdit(record)}>编辑</Button>
              <Popconfirm title="确定要删除吗?" onConfirm={() => this.handleDel(record)}>
                <Button size="small">删除</Button>
              </Popconfirm>
            </Button.Group>
          );
        }
      }
    ];

    return (
      <Table columns={columns} dataSource={userList} rowKey={row => row.id}/>
    );
  }
}

UserList.contextTypes = {
  router: React.PropTypes.object.isRequired
};

export default UserList;


================================================
FILE: src/reducers/index.js
================================================


================================================
FILE: src/reducers/user.js
================================================
export function dataSource (state = [], action) {
  switch (action.type) {

    default:
      return state;
  }
}

export function formValues (state = {}, action) {
  switch (action.type) {
    default:
      return state;
  }
}

export function editTarget (state = null, action) {
  switch (action.type) {
    default:
      return state;
  }
}


================================================
FILE: src/styles/auto-complete.less
================================================
.wrapper {
  display: inline-block;
  position: relative;
}

.options {
  z-index: 2;
  margin: 0;
  padding: 0;
  top: 110%;
  left: 0;
  right: 0;
  list-style: none;
  position: absolute;
  background-color: #fff;
  box-shadow: 1px 1px 10px 0 rgba(0, 0, 0, .6);

  > li {
    padding: 3px 6px;

    &.active {
      background-color: #0094ff;
      color: white;
    }
  }
}


================================================
FILE: src/styles/home-layout.less
================================================
.main {
  height: 100vh;
  padding-top: 50px;
}

.header {
  position: absolute;
  top: 0;
  height: 50px;
  width: 100%;
  font-size: 18px;
  padding: 0 20px;
  line-height: 50px;
  background-color: #108ee9;
  color: #fff;

  a {
    color: inherit;
  }
}

.menu {
  height: 100%;
  width: 240px;
  float: left;
  background-color: #404040;
}

.content {
  height: 100%;
  padding: 12px;
  overflow: auto;
  margin-left: 240px;
  align-self: stretch;
}


================================================
FILE: src/styles/home-page.less
================================================
.welcome {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 32px;
}


================================================
FILE: src/styles/login-page.less
================================================
.wrapper {
  height: 100vh;
  display: flex;
  align-items: center;
  justify-content: center;
}

.body {
  width: 360px;
  box-shadow: 1px 1px 10px 0 rgba(0, 0, 0, .3);
}

.header {
  color: #fff;
  font-size: 24px;
  padding: 30px 20px;
  background-color: #108ee9;
}

.form {
  margin-top: 12px;
  padding: 24px;
}

.btn {
  width: 100%;
}


================================================
FILE: src/type.js
================================================
export const USER_UPDATE_FORM = 'USER_UPDATE_FORM';

export const USER_DETAIL_REQUEST = 'USER_DETAIL_REQUEST';
export const USER_DETAIL_SUCCESS = 'USER_DETAIL_SUCCESS';
export const USER_DETAIL_FAILURE = 'USER_DETAIL_FAILURE';

export const USER_LIST_REQUEST = 'USER_LIST_REQUEST';
export const USER_LIST_SUCCESS = 'USER_LIST_SUCCESS';
export const USER_LIST_FAILURE = 'USER_LIST_FAILURE';

export const USER_ADD_REQUEST = 'USER_ADD_REQUEST';
export const USER_ADD_SUCCESS = 'USER_ADD_SUCCESS';
export const USER_ADD_FAILURE = 'USER_ADD_FAILURE';

export const USER_UPDATE_REQUEST = 'USER_UPDATE_REQUEST';
export const USER_UPDATE_SUCCESS = 'USER_UPDATE_SUCCESS';
export const USER_UPDATE_FAILURE = 'USER_UPDATE_FAILURE';

export const USER_DEL_REQUEST = 'USER_DEL_REQUEST';
export const USER_DEL_SUCCESS = 'USER_DEL_SUCCESS';
export const USER_DEL_FAILURE = 'USER_DEL_FAILURE';


================================================
FILE: src/utils/formProvider.js
================================================
import React from 'react';

function formProvider (fields) {
  return function (Comp) {

    const initialFormState = {};
    for (const key in fields) {
      initialFormState[key] = {
        value: fields[key].defaultValue,
        error: ''
      };
    }

    class FormComponent extends React.Component {
      constructor (props) {
        super(props);
        this.state = {
          form: initialFormState,
          formValid: false
        };

        this.handleValueChange = this.handleValueChange.bind(this);
        this.setFormValues = this.setFormValues.bind(this);
      }

      setFormValues (values) {
        if (!values) {
          return;
        }

        const {form} = this.state;
        let newForm = {...form};
        for (const field in form) {
          if (form.hasOwnProperty(field)) {
            if (typeof values[field] !== 'undefined') {
              newForm[field] = {...newForm[field], value: values[field]};
            }
            newForm[field].valid = true;
          }
        }

        this.setState({form: newForm});
      }

      handleValueChange (fieldName, value) {
        const { form } = this.state;
        const fieldState = form[fieldName];

        const newFieldState = {...fieldState, value, valid: true, error: ''};

        const fieldRules = fields[fieldName].rules;

        for (let i = 0; i < fieldRules.length; i++) {
          const {pattern, error} = fieldRules[i];
          let valid = false;
          if (typeof pattern === 'function') {
            valid = pattern(value);
          } else {
            valid = pattern.test(value);
          }

          if (!valid) {
            newFieldState.valid = false;
            newFieldState.error = error;
            break;
          }
        }

        const newForm = {...form, [fieldName]: newFieldState};
        const formValid = Object.values(newForm).every(f => f.valid);

        this.setState({
          form: newForm,
          formValid
        });
      }

      render () {
        const {form, formValid} = this.state;
        return (
          <Comp
            {...this.props}
            form={form}
            formValid={formValid}
            onFormChange={this.handleValueChange}
            setFormValues={this.setFormValues}
          />
        );
      }
    }

    return FormComponent;
  }
}

export default formProvider;


================================================
FILE: src/utils/request.js
================================================
import { hashHistory } from 'react-router';

export default function request (method, url, body) {
  method = method.toUpperCase();
  if (method === 'GET') {
    body = undefined;
  } else {
    body = body && JSON.stringify(body);
  }

  return fetch(url, {
    method,
    headers: {
      'Content-Type': 'application/json',
      'Accept': 'application/json',
      'Access-Token': sessionStorage.getItem('access_token') || ''
    },
    body
  })
    .then((res) => {
      if (res.status === 401) {
        hashHistory.push('/login');
        return Promise.reject('Unauthorized.');
      } else {
        const token = res.headers.get('access-token');
        if (token) {
          sessionStorage.setItem('access_token', token);
        }
        return res.json();
      }
    });
}

export const get = url => request('GET', url);
export const post = (url, body) => request('POST', url, body);
export const put = (url, body) => request('PUT', url, body);
export const del = (url, body) => request('DELETE', url, body);
Download .txt
gitextract_ra15fb77/

├── .gitignore
├── .roadhogrc
├── README.md
├── package.json
├── public/
│   └── index.html
├── server/
│   ├── auth.js
│   ├── db.json
│   └── index.js
└── src/
    ├── components/
    │   ├── AutoComplete.js
    │   ├── BookEditor.js
    │   ├── FormItem.js
    │   └── UserEditor.js
    ├── createStore.js
    ├── index.js
    ├── layouts/
    │   └── HomeLayout.js
    ├── pages/
    │   ├── BookAdd.js
    │   ├── BookEdit.js
    │   ├── BookList.js
    │   ├── Home.js
    │   ├── Login.js
    │   ├── UserAdd.js
    │   ├── UserEdit.js
    │   └── UserList.js
    ├── reducers/
    │   ├── index.js
    │   └── user.js
    ├── styles/
    │   ├── auto-complete.less
    │   ├── home-layout.less
    │   ├── home-page.less
    │   └── login-page.less
    ├── type.js
    └── utils/
        ├── formProvider.js
        └── request.js
Download .txt
SYMBOL INDEX (75 symbols across 17 files)

FILE: src/components/AutoComplete.js
  function getItemValue (line 5) | function getItemValue (item) {
  class AutoComplete (line 9) | class AutoComplete extends React.Component {
    method constructor (line 10) | constructor (props) {
    method handleChange (line 23) | handleChange (value) {
    method handleKeyDown (line 28) | handleKeyDown (e) {
    method moveItem (line 50) | moveItem (direction) {
    method handleEnter (line 79) | handleEnter (index) {
    method handleLeave (line 84) | handleLeave () {
    method render (line 88) | render () {

FILE: src/components/BookEditor.js
  class BookEditor (line 17) | class BookEditor extends React.Component {
    method constructor (line 18) | constructor (props) {
    method componentDidMount (line 27) | componentDidMount () {
    method handleSubmit (line 37) | handleSubmit (e) {
    method getRecommendUsers (line 70) | getRecommendUsers (partialUserId) {
    method handleOwnerIdChange (line 90) | handleOwnerIdChange (value) {
    method render (line 105) | render () {

FILE: src/components/FormItem.js
  class FormItem (line 3) | class FormItem extends React.Component {
    method render (line 4) | render () {

FILE: src/components/UserEditor.js
  class UserEditor (line 16) | class UserEditor extends React.Component {
    method componentDidMount (line 17) | componentDidMount () {
    method handleSubmit (line 27) | handleSubmit (e) {
    method render (line 60) | render () {

FILE: src/layouts/HomeLayout.js
  class HomeLayout (line 9) | class HomeLayout extends React.Component {
    method render (line 10) | render () {

FILE: src/pages/BookAdd.js
  class BookAdd (line 4) | class BookAdd extends React.Component {
    method render (line 5) | render () {

FILE: src/pages/BookEdit.js
  class BookEdit (line 5) | class BookEdit extends React.Component {
    method constructor (line 6) | constructor (props) {
    method componentWillMount (line 13) | componentWillMount () {
    method render (line 23) | render () {

FILE: src/pages/BookList.js
  class BookList (line 5) | class BookList extends React.Component {
    method constructor (line 6) | constructor (props) {
    method componentWillMount (line 13) | componentWillMount () {
    method handleEdit (line 22) | handleEdit (book) {
    method handleDel (line 26) | handleDel (book) {
    method render (line 40) | render () {

FILE: src/pages/Home.js
  class Home (line 4) | class Home extends React.Component {
    method render (line 5) | render () {

FILE: src/pages/Login.js
  class Login (line 8) | class Login extends React.Component {
    method constructor (line 9) | constructor () {
    method handleSubmit (line 14) | handleSubmit (e) {
    method render (line 32) | render () {

FILE: src/pages/UserAdd.js
  class UserAdd (line 4) | class UserAdd extends React.Component {
    method render (line 5) | render () {

FILE: src/pages/UserEdit.js
  class UserEdit (line 5) | class UserEdit extends React.Component {
    method constructor (line 6) | constructor (props) {
    method componentWillMount (line 13) | componentWillMount () {
    method render (line 23) | render () {

FILE: src/pages/UserList.js
  class UserList (line 5) | class UserList extends React.Component {
    method constructor (line 6) | constructor (props) {
    method componentWillMount (line 13) | componentWillMount () {
    method handleEdit (line 22) | handleEdit (user) {
    method handleDel (line 26) | handleDel (user) {
    method render (line 40) | render () {

FILE: src/reducers/user.js
  function dataSource (line 1) | function dataSource (state = [], action) {
  function formValues (line 9) | function formValues (state = {}, action) {
  function editTarget (line 16) | function editTarget (state = null, action) {

FILE: src/type.js
  constant USER_UPDATE_FORM (line 1) | const USER_UPDATE_FORM = 'USER_UPDATE_FORM';
  constant USER_DETAIL_REQUEST (line 3) | const USER_DETAIL_REQUEST = 'USER_DETAIL_REQUEST';
  constant USER_DETAIL_SUCCESS (line 4) | const USER_DETAIL_SUCCESS = 'USER_DETAIL_SUCCESS';
  constant USER_DETAIL_FAILURE (line 5) | const USER_DETAIL_FAILURE = 'USER_DETAIL_FAILURE';
  constant USER_LIST_REQUEST (line 7) | const USER_LIST_REQUEST = 'USER_LIST_REQUEST';
  constant USER_LIST_SUCCESS (line 8) | const USER_LIST_SUCCESS = 'USER_LIST_SUCCESS';
  constant USER_LIST_FAILURE (line 9) | const USER_LIST_FAILURE = 'USER_LIST_FAILURE';
  constant USER_ADD_REQUEST (line 11) | const USER_ADD_REQUEST = 'USER_ADD_REQUEST';
  constant USER_ADD_SUCCESS (line 12) | const USER_ADD_SUCCESS = 'USER_ADD_SUCCESS';
  constant USER_ADD_FAILURE (line 13) | const USER_ADD_FAILURE = 'USER_ADD_FAILURE';
  constant USER_UPDATE_REQUEST (line 15) | const USER_UPDATE_REQUEST = 'USER_UPDATE_REQUEST';
  constant USER_UPDATE_SUCCESS (line 16) | const USER_UPDATE_SUCCESS = 'USER_UPDATE_SUCCESS';
  constant USER_UPDATE_FAILURE (line 17) | const USER_UPDATE_FAILURE = 'USER_UPDATE_FAILURE';
  constant USER_DEL_REQUEST (line 19) | const USER_DEL_REQUEST = 'USER_DEL_REQUEST';
  constant USER_DEL_SUCCESS (line 20) | const USER_DEL_SUCCESS = 'USER_DEL_SUCCESS';
  constant USER_DEL_FAILURE (line 21) | const USER_DEL_FAILURE = 'USER_DEL_FAILURE';

FILE: src/utils/formProvider.js
  function formProvider (line 3) | function formProvider (fields) {

FILE: src/utils/request.js
  function request (line 3) | function request (method, url, body) {
Condensed preview — 32 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (35K chars).
[
  {
    "path": ".gitignore",
    "chars": 32,
    "preview": ".idea\n.vscode\nnode_modules\ndist\n"
  },
  {
    "path": ".roadhogrc",
    "chars": 140,
    "preview": "{\n  \"extraBabelPlugins\": [\n    [\"import\", {\n      \"libraryName\": \"antd\",\n      \"libraryDirectory\": \"lib\",\n      \"style\":"
  },
  {
    "path": "README.md",
    "chars": 939,
    "preview": "# React全家桶入门系列文章项目\n\n本教程涵盖React主流技术栈:\n\n- react - [github](https://github.com/facebook/react)\n- react-router - [github](ht"
  },
  {
    "path": "package.json",
    "chars": 520,
    "preview": "{\n  \"name\": \"react-curd\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"src/index.js\",\n  \"scripts\": {\n    \"serve"
  },
  {
    "path": "public/index.html",
    "chars": 381,
    "preview": "<!doctype html>\n<html lang=\"zh-cn\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\"\n        content=\"width=devic"
  },
  {
    "path": "server/auth.js",
    "chars": 474,
    "preview": "const expireTime = 1000 * 60;\n\nmodule.exports = function (req, res, next) {\n  res.header('Access-Control-Expose-Headers'"
  },
  {
    "path": "server/db.json",
    "chars": 429,
    "preview": "{\n  \"user\": [\n    {\n      \"id\": 10000,\n      \"name\": \"一韬\",\n      \"age\": 25,\n      \"gender\": \"male\"\n    },\n    {\n      \"i"
  },
  {
    "path": "server/index.js",
    "chars": 749,
    "preview": "const path = require('path');\nconst jsonServer = require('json-server');\nconst server = jsonServer.create();\nconst route"
  },
  {
    "path": "src/components/AutoComplete.js",
    "chars": 3241,
    "preview": "import React, { PropTypes } from 'react';\nimport { Input } from 'antd';\nimport style from '../styles/auto-complete.less'"
  },
  {
    "path": "src/components/BookEditor.js",
    "chars": 4199,
    "preview": "import React from 'react';\nimport { Input, InputNumber, Form, Button, message } from 'antd';\nimport AutoComplete from '."
  },
  {
    "path": "src/components/FormItem.js",
    "chars": 307,
    "preview": "import React from 'react';\n\nclass FormItem extends React.Component {\n  render () {\n    const {label, children, valid, er"
  },
  {
    "path": "src/components/UserEditor.js",
    "chars": 3381,
    "preview": "import React from 'react';\nimport { Form, Input, InputNumber, Select, Button, message } from 'antd';\nimport request from"
  },
  {
    "path": "src/createStore.js",
    "chars": 86,
    "preview": "import { combineReducers } from 'redux';\n\nexport default function (initialState) {\n\n}\n"
  },
  {
    "path": "src/index.js",
    "chars": 1103,
    "preview": "import React from 'react';\nimport ReactDOM from 'react-dom';\nimport { Router, Route, hashHistory } from 'react-router';\n"
  },
  {
    "path": "src/layouts/HomeLayout.js",
    "chars": 1491,
    "preview": "import React from 'react';\nimport { Link } from 'react-router';\nimport { Menu, Icon } from 'antd';\nimport style from '.."
  },
  {
    "path": "src/pages/BookAdd.js",
    "chars": 204,
    "preview": "import React from 'react';\nimport BookEditor from '../components/BookEditor';\n\nclass BookAdd extends React.Component {\n "
  },
  {
    "path": "src/pages/BookEdit.js",
    "chars": 690,
    "preview": "import React from 'react';\nimport BookEditor from '../components/BookEditor';\nimport { get } from '../utils/request';\n\nc"
  },
  {
    "path": "src/pages/BookList.js",
    "chars": 1860,
    "preview": "import React from 'react';\nimport { message, Table, Button, Popconfirm } from 'antd';\nimport { get, del } from '../utils"
  },
  {
    "path": "src/pages/Home.js",
    "chars": 240,
    "preview": "import React from 'react';\nimport style from '../styles/home-page.less';\n\nclass Home extends React.Component {\n  render "
  },
  {
    "path": "src/pages/Login.js",
    "chars": 2250,
    "preview": "import React from 'react';\nimport { Icon, Form, Input, Button, message } from 'antd';\nimport { post } from '../utils/req"
  },
  {
    "path": "src/pages/UserAdd.js",
    "chars": 204,
    "preview": "import React from 'react';\nimport UserEditor from '../components/UserEditor';\n\nclass UserAdd extends React.Component {\n "
  },
  {
    "path": "src/pages/UserEdit.js",
    "chars": 690,
    "preview": "import React from 'react';\nimport UserEditor from '../components/UserEditor';\nimport { get } from '../utils/request';\n\nc"
  },
  {
    "path": "src/pages/UserList.js",
    "chars": 1825,
    "preview": "import React from 'react';\nimport { message, Table, Button, Popconfirm } from 'antd';\nimport { get, del } from '../utils"
  },
  {
    "path": "src/reducers/index.js",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "src/reducers/user.js",
    "chars": 347,
    "preview": "export function dataSource (state = [], action) {\n  switch (action.type) {\n\n    default:\n      return state;\n  }\n}\n\nexpo"
  },
  {
    "path": "src/styles/auto-complete.less",
    "chars": 378,
    "preview": ".wrapper {\n  display: inline-block;\n  position: relative;\n}\n\n.options {\n  z-index: 2;\n  margin: 0;\n  padding: 0;\n  top: "
  },
  {
    "path": "src/styles/home-layout.less",
    "chars": 455,
    "preview": ".main {\n  height: 100vh;\n  padding-top: 50px;\n}\n\n.header {\n  position: absolute;\n  top: 0;\n  height: 50px;\n  width: 100%"
  },
  {
    "path": "src/styles/home-page.less",
    "chars": 130,
    "preview": ".welcome {\n  width: 100%;\n  height: 100%;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  font-size"
  },
  {
    "path": "src/styles/login-page.less",
    "chars": 343,
    "preview": ".wrapper {\n  height: 100vh;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\n.body {\n  width: 360px"
  },
  {
    "path": "src/type.js",
    "chars": 879,
    "preview": "export const USER_UPDATE_FORM = 'USER_UPDATE_FORM';\n\nexport const USER_DETAIL_REQUEST = 'USER_DETAIL_REQUEST';\nexport co"
  },
  {
    "path": "src/utils/formProvider.js",
    "chars": 2383,
    "preview": "import React from 'react';\n\nfunction formProvider (fields) {\n  return function (Comp) {\n\n    const initialFormState = {}"
  },
  {
    "path": "src/utils/request.js",
    "chars": 1028,
    "preview": "import { hashHistory } from 'react-router';\n\nexport default function request (method, url, body) {\n  method = method.toU"
  }
]

About this extraction

This page contains the full source code of the awaw00/react-curd GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 32 files (30.6 KB), approximately 8.6k tokens, and a symbol index with 75 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!