提交 f78c36093da26251e71c75ac4a2f8582fe37b758
提交者
Yu
1 个父辈
5609d81a
feat: add user dashboard and simplify the block (#13)
正在显示
15 个修改的文件
包含
436 行增加
和
66 行删除
blank/LICENSE
已删除
100644 → 0
1 | -The MIT License (MIT) | |
2 | - | |
3 | -Copyright (c) 2018-present yutingzhao1991 (yutingzhao1991@sina.com) | |
4 | - | |
5 | -Permission is hereby granted, free of charge, to any person obtaining a copy | |
6 | -of this software and associated documentation files (the "Software"), to deal | |
7 | -in the Software without restriction, including without limitation the rights | |
8 | -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
9 | -copies of the Software, and to permit persons to whom the Software is | |
10 | -furnished to do so, subject to the following conditions: | |
11 | - | |
12 | -The above copyright notice and this permission notice shall be included in | |
13 | -all copies or substantial portions of the Software. | |
14 | - | |
15 | -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
16 | -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
17 | -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
18 | -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
19 | -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
20 | -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
21 | -THE SOFTWARE. |
1 | 1 | { |
2 | 2 | "name": "@umi-block/blank", |
3 | - "version": "0.0.1", | |
4 | 3 | "description": "A blank block for quick start a umi page develop.", |
5 | - "main": "src/index.js", | |
6 | - "scripts": { | |
7 | - "dev": "umi dev" | |
8 | - }, | |
9 | - "repository": { | |
10 | - "type": "git", | |
11 | - "url": "https://github.com/umijs/umi-blocks/blank" | |
12 | - }, | |
13 | 4 | "dependencies": { |
14 | - "antd": "^3.0.0", | |
15 | - "dva": "^2.0.0", | |
16 | - "react": ">=16.0.0", | |
17 | 5 | "umi-request": "^1.0.0" |
18 | - }, | |
19 | - "devDependencies": { | |
20 | - "umi": "^2.3.0-beta.1", | |
21 | - "umi-plugin-block-dev": "^1.0.0", | |
22 | - "umi-plugin-react": "^1.3.0-beta.1" | |
23 | - }, | |
24 | - "authors": [ | |
25 | - "yutingzhao1991 <yutingzhao1991@sina.com>" | |
26 | - ], | |
27 | - "license": "ISC" | |
6 | + } | |
28 | 7 | } | ... | ... |
1 | 1 | { |
2 | 2 | "name": "@umi-blocks/demo", |
3 | - "version": "1.0.0", | |
4 | - "description": "a demo block of umi", | |
5 | - "main": "src/index.js", | |
6 | - "scripts": { | |
7 | - "dev": "umi dev" | |
8 | - }, | |
9 | - "repository": { | |
10 | - "type": "git", | |
11 | - "url": "https://github.com/umijs/umi-blocks/demo" | |
12 | - }, | |
13 | - "dependencies": { | |
14 | - "react": ">=16.0.0", | |
15 | - "antd": "^3.0.0" | |
16 | - }, | |
17 | - "devDependencies": { | |
18 | - "umi": "^2.0.0", | |
19 | - "umi-plugin-block-dev": "^1.0.0", | |
20 | - "umi-plugin-react": "^1.2.3" | |
21 | - }, | |
22 | - "author": "yutingzhao1991 <yutingzhao1991@sina.com>", | |
23 | - "license": "ISC" | |
3 | + "description": "a demo block of umi" | |
24 | 4 | } | ... | ... |
... | ... | @@ -25,14 +25,17 @@ |
25 | 25 | "prettier": "1.15.2", |
26 | 26 | "stylelint": "^9.8.0", |
27 | 27 | "stylelint-config-prettier": "^4.0.0", |
28 | - "stylelint-config-standard": "^18.0.0" | |
28 | + "stylelint-config-standard": "^18.0.0", | |
29 | + "umi": "^2.3.0-0", | |
30 | + "umi-plugin-block-dev": "^1.0.0", | |
31 | + "umi-plugin-react": "^1.3.0-0" | |
29 | 32 | }, |
30 | 33 | "lint-staged": { |
31 | 34 | "x/**/*.{js,ts,tsx,json,jsx,less}": [ |
32 | 35 | "node ./_scripts/lint-prettier.js", |
33 | 36 | "git add" |
34 | 37 | ], |
35 | - "**/*.{js,jsx}": "npm run lint-staged:js", | |
38 | + "x/**/*.{js,jsx}": "npm run lint-staged:js", | |
36 | 39 | "**/*.less": "stylelint --syntax less" |
37 | 40 | }, |
38 | 41 | "husky": { | ... | ... |
user-dashboard/.umirc.js
0 → 100644
user-dashboard/package.json
0 → 100644
user-dashboard/snapshot.png
0 → 100644
85.6 KB
user-dashboard/src/_mock.js
0 → 100644
1 | + | |
2 | +const PAGE_SIZE = 3; | |
3 | +let data = [ | |
4 | + { | |
5 | + "id": 1, | |
6 | + "name": "Leanne Graham", | |
7 | + "email": "Sincere@april.biz", | |
8 | + "website": "hildegard.org", | |
9 | + }, | |
10 | + { | |
11 | + "id": 2, | |
12 | + "name": "Ervin Howell", | |
13 | + "email": "Shanna@melissa.tv", | |
14 | + "website": "anastasia.net", | |
15 | + }, | |
16 | + { | |
17 | + "id": 3, | |
18 | + "name": "Clementine Bauch", | |
19 | + "email": "Nathan@yesenia.net", | |
20 | + "website": "ramiro.info", | |
21 | + }, | |
22 | + { | |
23 | + "id": 4, | |
24 | + "name": "Patricia Lebsack", | |
25 | + "email": "Julianne.OConner@kory.org", | |
26 | + "website": "kale.biz", | |
27 | + }, | |
28 | + { | |
29 | + "id": 5, | |
30 | + "name": "Chelsey Dietrich", | |
31 | + "email": "Lucio_Hettinger@annie.ca", | |
32 | + "website": "demarco.info", | |
33 | + }, | |
34 | + { | |
35 | + "id": 6, | |
36 | + "name": "Mrs. Dennis Schulist", | |
37 | + "email": "Karley_Dach@jasper.info", | |
38 | + "website": "ola.org", | |
39 | + }, | |
40 | + { | |
41 | + "id": 7, | |
42 | + "name": "Kurtis Weissnat", | |
43 | + "email": "Telly.Hoeger@billy.biz", | |
44 | + "website": "elvis.io", | |
45 | + }, | |
46 | + { | |
47 | + "id": 8, | |
48 | + "name": "Nicholas Runolfsdottir V", | |
49 | + "email": "Sherwood@rosamond.me", | |
50 | + "website": "jacynthe.com", | |
51 | + }, | |
52 | + { | |
53 | + "id": 9, | |
54 | + "name": "Glenna Reichert", | |
55 | + "email": "Chaim_McDermott@dana.io", | |
56 | + "website": "conrad.com", | |
57 | + }, | |
58 | +]; | |
59 | + | |
60 | +function uid(len) { | |
61 | + len = len || 7; | |
62 | + return Math.random().toString(35).substr(2, len); | |
63 | +} | |
64 | + | |
65 | +function getData(page) { | |
66 | + const start = (page - 1) * PAGE_SIZE; | |
67 | + return { | |
68 | + status: 'success', | |
69 | + total: data.length, | |
70 | + page, | |
71 | + data: data.slice(start, start + 3), | |
72 | + }; | |
73 | +} | |
74 | + | |
75 | +export default { | |
76 | + 'GET /api/BLOCK_NAME': (req, res) => { | |
77 | + res.json(getData(parseInt(req.query.page, 10) || 1)); | |
78 | + }, | |
79 | + 'DELETE /api/BLOCK_NAME/:id': (req, res) => { | |
80 | + data = data.filter(item => `${item.id}` !== `${req.params.id}`); | |
81 | + res.end('ok'); | |
82 | + }, | |
83 | + 'PATCH /api/BLOCK_NAME/:id': (req, res) => { | |
84 | + data.forEach(item => { | |
85 | + if (`${item.id}` === `${req.params.id}`) { | |
86 | + Object.assign(item, req.body); | |
87 | + } | |
88 | + }); | |
89 | + res.end('ok'); | |
90 | + }, | |
91 | + 'POST /api/BLOCK_NAME': (req, res) => { | |
92 | + data.push({ | |
93 | + ...req.body, | |
94 | + id: uid(), | |
95 | + }); | |
96 | + res.end('ok'); | |
97 | + }, | |
98 | +}; | ... | ... |
user-dashboard/src/components/UserModal.js
0 → 100644
1 | +import { Component } from 'react'; | |
2 | +import { Modal, Form, Input } from 'antd'; | |
3 | + | |
4 | +const FormItem = Form.Item; | |
5 | + | |
6 | +class UserEditModal extends Component { | |
7 | + constructor(props) { | |
8 | + super(props); | |
9 | + this.state = { | |
10 | + visible: false, | |
11 | + }; | |
12 | + } | |
13 | + | |
14 | + showModelHandler = (e) => { | |
15 | + if (e) e.stopPropagation(); | |
16 | + this.setState({ | |
17 | + visible: true, | |
18 | + }); | |
19 | + }; | |
20 | + | |
21 | + hideModelHandler = () => { | |
22 | + this.setState({ | |
23 | + visible: false, | |
24 | + }); | |
25 | + }; | |
26 | + | |
27 | + okHandler = () => { | |
28 | + const { onOk } = this.props; | |
29 | + this.props.form.validateFields((err, values) => { | |
30 | + if (!err) { | |
31 | + onOk(values); | |
32 | + this.hideModelHandler(); | |
33 | + } | |
34 | + }); | |
35 | + }; | |
36 | + | |
37 | + render() { | |
38 | + const { children } = this.props; | |
39 | + const { getFieldDecorator } = this.props.form; | |
40 | + const { name, email, website } = this.props.record; | |
41 | + const formItemLayout = { | |
42 | + labelCol: { span: 6 }, | |
43 | + wrapperCol: { span: 14 }, | |
44 | + }; | |
45 | + | |
46 | + return ( | |
47 | + <span> | |
48 | + <span onClick={this.showModelHandler}> | |
49 | + { children } | |
50 | + </span> | |
51 | + <Modal | |
52 | + title="Edit User" | |
53 | + visible={this.state.visible} | |
54 | + onOk={this.okHandler} | |
55 | + onCancel={this.hideModelHandler} | |
56 | + > | |
57 | + <Form horizontal onSubmit={this.okHandler}> | |
58 | + <FormItem | |
59 | + {...formItemLayout} | |
60 | + label="Name" | |
61 | + > | |
62 | + { | |
63 | + getFieldDecorator('name', { | |
64 | + initialValue: name, | |
65 | + })(<Input />) | |
66 | + } | |
67 | + </FormItem> | |
68 | + <FormItem | |
69 | + {...formItemLayout} | |
70 | + label="Email" | |
71 | + > | |
72 | + { | |
73 | + getFieldDecorator('email', { | |
74 | + initialValue: email, | |
75 | + })(<Input />) | |
76 | + } | |
77 | + </FormItem> | |
78 | + <FormItem | |
79 | + {...formItemLayout} | |
80 | + label="Website" | |
81 | + > | |
82 | + { | |
83 | + getFieldDecorator('website', { | |
84 | + initialValue: website, | |
85 | + })(<Input />) | |
86 | + } | |
87 | + </FormItem> | |
88 | + </Form> | |
89 | + </Modal> | |
90 | + </span> | |
91 | + ); | |
92 | + } | |
93 | +} | |
94 | + | |
95 | +export default Form.create()(UserEditModal); | ... | ... |
user-dashboard/src/components/Users.css
0 → 100644
user-dashboard/src/components/Users.js
0 → 100644
1 | +import { connect } from 'dva'; | |
2 | +import { Table, Pagination, Popconfirm, Button } from 'antd'; | |
3 | +import { routerRedux } from 'dva/router'; | |
4 | +import styles from './Users.css'; | |
5 | +import { PAGE_SIZE } from '../utils/constants'; | |
6 | +import UserModal from './UserModal'; | |
7 | + | |
8 | +function Users({ dispatch, list: dataSource, loading, total, page: current }) { | |
9 | + function deleteHandler(id) { | |
10 | + dispatch({ | |
11 | + type: 'BLOCK_NAME/remove', | |
12 | + payload: id, | |
13 | + }); | |
14 | + } | |
15 | + | |
16 | + function pageChangeHandler(page) { | |
17 | + dispatch({ | |
18 | + type: 'BLOCK_NAME/fetch', | |
19 | + payload: { page }, | |
20 | + }); | |
21 | + } | |
22 | + | |
23 | + function editHandler(id, values) { | |
24 | + dispatch({ | |
25 | + type: 'BLOCK_NAME/patch', | |
26 | + payload: { id, values }, | |
27 | + }); | |
28 | + } | |
29 | + | |
30 | + function createHandler(values) { | |
31 | + dispatch({ | |
32 | + type: 'BLOCK_NAME/create', | |
33 | + payload: values, | |
34 | + }); | |
35 | + } | |
36 | + | |
37 | + const columns = [ | |
38 | + { | |
39 | + title: 'Name', | |
40 | + dataIndex: 'name', | |
41 | + key: 'name', | |
42 | + render: text => <a href="">{text}</a>, | |
43 | + }, | |
44 | + { | |
45 | + title: 'Email', | |
46 | + dataIndex: 'email', | |
47 | + key: 'email', | |
48 | + }, | |
49 | + { | |
50 | + title: 'Website', | |
51 | + dataIndex: 'website', | |
52 | + key: 'website', | |
53 | + }, | |
54 | + { | |
55 | + title: 'Operation', | |
56 | + key: 'operation', | |
57 | + render: (text, record) => ( | |
58 | + <span className={styles.operation}> | |
59 | + <UserModal record={record} onOk={editHandler.bind(null, record.id)}> | |
60 | + <a>Edit</a> | |
61 | + </UserModal> | |
62 | + <Popconfirm title="Confirm to delete?" onConfirm={deleteHandler.bind(null, record.id)}> | |
63 | + <a href="">Delete</a> | |
64 | + </Popconfirm> | |
65 | + </span> | |
66 | + ), | |
67 | + }, | |
68 | + ]; | |
69 | + | |
70 | + return ( | |
71 | + <div className={styles.normal}> | |
72 | + <div> | |
73 | + <div className={styles.create}> | |
74 | + <UserModal record={{}} onOk={createHandler}> | |
75 | + <Button type="primary">Create User</Button> | |
76 | + </UserModal> | |
77 | + </div> | |
78 | + <Table | |
79 | + loading={loading} | |
80 | + columns={columns} | |
81 | + dataSource={dataSource} | |
82 | + rowKey={record => record.id} | |
83 | + pagination={false} | |
84 | + /> | |
85 | + <Pagination | |
86 | + className="ant-table-pagination" | |
87 | + total={total} | |
88 | + current={current} | |
89 | + pageSize={PAGE_SIZE} | |
90 | + onChange={pageChangeHandler} | |
91 | + /> | |
92 | + </div> | |
93 | + </div> | |
94 | + ); | |
95 | +} | |
96 | + | |
97 | +function mapStateToProps(state) { | |
98 | + const { list, total, page } = state['BLOCK_NAME']; | |
99 | + return { | |
100 | + list, | |
101 | + total, | |
102 | + page, | |
103 | + loading: state.loading.models['BLOCK_NAME'], | |
104 | + }; | |
105 | +} | |
106 | + | |
107 | +export default connect(mapStateToProps)(Users); | ... | ... |
user-dashboard/src/index.js
0 → 100644
1 | +import { Component } from 'react'; | |
2 | +import { connect } from 'dva'; | |
3 | +import Users from './components/Users'; | |
4 | + | |
5 | +class BLOCK_NAME extends Component { | |
6 | + componentDidMount() { | |
7 | + this.props.dispatch({ | |
8 | + type: 'BLOCK_NAME/fetch', | |
9 | + payload: { | |
10 | + page: 1, | |
11 | + }, | |
12 | + }); | |
13 | + } | |
14 | + | |
15 | + render() { | |
16 | + return ( | |
17 | + <div> | |
18 | + <Users /> | |
19 | + </div> | |
20 | + ); | |
21 | + } | |
22 | +} | |
23 | + | |
24 | +export default connect()(BLOCK_NAME); | ... | ... |
user-dashboard/src/model.js
0 → 100644
1 | +import * as usersService from './service'; | |
2 | + | |
3 | +export default { | |
4 | + namespace: 'BLOCK_NAME', | |
5 | + state: { | |
6 | + list: [], | |
7 | + total: null, | |
8 | + page: null, | |
9 | + }, | |
10 | + reducers: { | |
11 | + save(state, { payload: { data: list, total, page } }) { | |
12 | + return { ...state, list, total, page }; | |
13 | + }, | |
14 | + }, | |
15 | + effects: { | |
16 | + *fetch({ payload: { page = 1 } }, { call, put }) { | |
17 | + const { data, total, page: currentPage } = yield call(usersService.fetch, { page }); | |
18 | + yield put({ | |
19 | + type: 'save', | |
20 | + payload: { | |
21 | + data, | |
22 | + total, | |
23 | + page: currentPage, | |
24 | + }, | |
25 | + }); | |
26 | + }, | |
27 | + *remove({ payload: id }, { call, put, select }) { | |
28 | + yield call(usersService.remove, id); | |
29 | + const page = yield select(state => state['BLOCK_NAME'].page); | |
30 | + yield put({ type: 'fetch', payload: { page } }); | |
31 | + }, | |
32 | + *patch({ payload: { id, values } }, { call, put, select }) { | |
33 | + yield call(usersService.patch, id, values); | |
34 | + const page = yield select(state => state['BLOCK_NAME'].page); | |
35 | + yield put({ type: 'fetch', payload: { page } }); | |
36 | + }, | |
37 | + *create({ payload: values }, { call, put, select }) { | |
38 | + yield call(usersService.create, values); | |
39 | + const page = yield select(state => state['BLOCK_NAME'].page); | |
40 | + yield put({ type: 'fetch', payload: { page } }); | |
41 | + }, | |
42 | + }, | |
43 | +}; | ... | ... |
user-dashboard/src/service.js
0 → 100644
1 | +import request from 'umi-request'; | |
2 | + | |
3 | +const API_PREFIX = `/api/BLOCK_NAME`; | |
4 | + | |
5 | +export function fetch({ page = 1 }) { | |
6 | + return request(`${API_PREFIX}?page=${page}`); | |
7 | +} | |
8 | + | |
9 | +export function remove(id) { | |
10 | + return request(`${API_PREFIX}/${id}`, { | |
11 | + method: 'DELETE', | |
12 | + }); | |
13 | +} | |
14 | + | |
15 | +export function patch(id, values) { | |
16 | + // TODO: | |
17 | + // use umi-request after the issue is closed | |
18 | + // https://github.com/umijs/umi-request/issues/5 | |
19 | + return window.fetch(`${API_PREFIX}/${id}`, { | |
20 | + method: 'PATCH', | |
21 | + headers: { | |
22 | + 'Content-Type': 'application/json', | |
23 | + }, | |
24 | + body: JSON.stringify(values), | |
25 | + }); | |
26 | +} | |
27 | + | |
28 | +export function create(values) { | |
29 | + return request(API_PREFIX, { | |
30 | + method: 'POST', | |
31 | + data: values, | |
32 | + }); | |
33 | +} | ... | ... |
user-dashboard/src/utils/constants.js
0 → 100644
请
注册
或
登录
后发表评论