├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
└── manifest.json
└── src
├── App.css
├── App.js
├── App.test.js
├── components
├── Label
│ ├── CheckboxLabel
│ │ └── index.jsx
│ └── LabelTags
│ │ └── index.jsx
├── Marker
│ ├── MarkerCreate
│ │ └── index.jsx
│ ├── MarkerList
│ │ └── index.jsx
│ └── ModalView
│ │ └── index.jsx
├── Statistic
│ └── index.jsx
└── common
│ ├── HighOrderComponents
│ └── AsyncComponent.js
│ ├── IconLike
│ └── index.jsx
│ ├── LoginForm
│ ├── index.jsx
│ └── index.less
│ └── UserAvatar
│ └── index.jsx
├── config
├── api.config.js
├── list.config.js
├── marker.config.js
└── routes.config.js
├── css
└── main.less
├── index.css
├── index.js
├── logo.svg
├── pages
├── Layouts.jsx
├── Menus.jsx
├── PageHome.jsx
├── PageLabel.jsx
├── PageList.jsx
├── PageLogin.jsx
├── PageMarker.jsx
└── PageSetting.jsx
├── registerServiceWorker.js
└── utils
├── asyncFetch.js
└── common.js
/README.md:
--------------------------------------------------------------------------------
1 | 使用React+Ant Design+react-router-dom,构建菜单栏及点击跳转的单页应用。
2 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "marker",
3 | "version": "0.1.0",
4 | "private": true,
5 | "babel": {
6 | "presets": [
7 | "react-app"
8 | ],
9 | "plugins": [
10 | "react-hot-loader/babel",
11 | "transform-runtime",
12 | [
13 | "import",
14 | {
15 | "libraryName": "antd",
16 | "style": true
17 | }
18 | ]
19 | ]
20 | },
21 | "dependencies": {
22 | "antd": "^3.13.0",
23 | "babel-core": "6.26.0",
24 | "babel-eslint": "7.2.3",
25 | "babel-jest": "20.0.3",
26 | "babel-loader": "7.1.2",
27 | "babel-preset-react-app": "^3.1.0",
28 | "babel-runtime": "6.26.0",
29 | "less": "^2.7.3",
30 | "less-loader": "^4.0.5",
31 | "react": "^16.3.2",
32 | "react-dom": "^16.3.2",
33 | "react-hot-loader": "^3.0.0",
34 | "react-router-dom": "^4.2.2",
35 | "react-scripts": "1.1.4"
36 | },
37 | "scripts": {
38 | "start": "react-scripts start",
39 | "build": "react-scripts build",
40 | "test": "react-scripts test --env=jsdom",
41 | "eject": "react-scripts eject"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PoldiChen/marker-react/bc0b635351571c4d6b9e32ae1ab636d76d7c90df/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
12 |
13 |
22 | React App
23 |
24 |
25 |
28 |
29 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | animation: App-logo-spin infinite 20s linear;
7 | height: 80px;
8 | }
9 |
10 | .App-header {
11 | background-color: #222;
12 | height: 150px;
13 | padding: 20px;
14 | color: white;
15 | }
16 |
17 | .App-title {
18 | font-size: 1.5em;
19 | }
20 |
21 | .App-intro {
22 | font-size: large;
23 | }
24 |
25 | .login-card {
26 | position: absolute;
27 | top: 50%;
28 | left: 50%;
29 | width: 500px;
30 | padding: 20px;
31 | background: #393;
32 | color: #fff;
33 | transform: translate(-50%, 40%);
34 | }
35 |
36 | @keyframes App-logo-spin {
37 | from { transform: rotate(0deg); }
38 | to { transform: rotate(360deg); }
39 | }
40 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import logo from './logo.svg';
3 | import PropTypes from "prop-types";
4 | import './App.css';
5 | import "./css/main.less";
6 | import Layouts from "./pages/Layouts";
7 | import PageLogin from "./pages/PageLogin";
8 | import { LocaleProvider, message } from "antd";
9 | import enUs from 'antd/lib/locale-provider/zh_CN'
10 | import { API } from "./config/api.config";
11 | import asyncFetch from "./utils/asyncFetch";
12 |
13 | class App extends Component {
14 |
15 | constructor(props) {
16 | console.log("App.js@constructor");
17 | console.log(props);
18 | super(props);
19 | this.state = {
20 | login: false,
21 | userInfo: {},
22 | token: ''
23 | }
24 | }
25 |
26 | componentDidMount() {
27 | console.log("App.js@componentDidMount");
28 | console.log(this.state.token);
29 | console.log(this.state.userInfo);
30 | let url = API.get_current_user;
31 | asyncFetch('GET', url, {},
32 | (res) => {
33 | console.log(res);
34 | if (res.code === 0) {
35 | message.success("current user.");
36 | message.success(JSON.stringify(res.data));
37 | this.setState(
38 | {
39 | login: true,
40 | userInfo: res.data
41 | }
42 | );
43 | } else {
44 | message.error(res.message);
45 | }
46 | }, {}, 'cors', () => {}, false, localStorage.getItem("markerToken"));
47 | }
48 |
49 | getChildContext() {
50 | return {
51 | login: this.state.login,
52 | userInfo: this.state.userInfo,
53 | token: this.state.token,
54 | setLoginInfo: this.setLoginInfo
55 | };
56 | }
57 |
58 | setLoginInfo = (login, userInfo, token) => {
59 | console.log('App.js@setLoginInfo');
60 | console.log(login);
61 | console.log(userInfo);
62 | console.log(token);
63 | localStorage.setItem("markerToken", token);
64 | this.setState(
65 | {
66 | login: login,
67 | userInfo: userInfo,
68 | token: token
69 | }
70 | );
71 | };
72 |
73 | render() {
74 | console.log("App.js@render");
75 | console.log(this.state.login);
76 | return (
77 |
78 | {
79 | this.state.login?
80 |
83 | :
84 |
87 | }
88 |
89 |
90 | );
91 | }
92 | }
93 |
94 | App.childContextTypes = {
95 | login: PropTypes.bool,
96 | userInfo: PropTypes.object,
97 | setLoginInfo: PropTypes.func,
98 | token: PropTypes.string
99 | };
100 |
101 |
102 | export default App;
103 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render(, div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/src/components/Label/CheckboxLabel/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Checkbox, message } from "antd";
3 | import { API } from "../../../config/api.config";
4 | import asyncFetch from "../../../utils/asyncFetch";
5 |
6 | class CheckboxLabel extends React.Component {
7 |
8 | constructor(props) {
9 | super(props);
10 | this.state = {
11 | options: []
12 | };
13 | }
14 |
15 | componentDidMount() {
16 | this.getLabels();
17 | }
18 |
19 | handleCheckOnChange = (e) => {
20 | console.log(e);
21 | this.props.onChange(e); // 调用父组件的函数
22 | };
23 |
24 | getLabels() {
25 | let url = API.get_labels;
26 | asyncFetch('GET', url, {},
27 | (res) => {
28 | if (res.code === 0) {
29 | let options = [];
30 | res.data.map(function(label) {
31 | options.push({
32 | value: label.id,
33 | label: label.name
34 | });
35 | });
36 | this.setState({
37 | options: options
38 | });
39 | } else {
40 | message.error(res.message);
41 | }
42 | }, {}, 'cors');
43 | }
44 |
45 | render() {
46 | const CheckboxGroup = Checkbox.Group;
47 |
48 | return (
49 |
50 | );
51 | }
52 |
53 | }
54 |
55 | export default CheckboxLabel;
--------------------------------------------------------------------------------
/src/components/Label/LabelTags/index.jsx:
--------------------------------------------------------------------------------
1 |
2 | import React from "react";
3 | import { Tag, message, Input } from 'antd';
4 | import { API } from "../../../config/api.config";
5 | import asyncFetch from "../../../utils/asyncFetch";
6 | import getColor from "../../../utils/common";
7 | import _ from "lodash";
8 |
9 | class LabelTags extends React.Component {
10 |
11 | constructor(props) {
12 | super(props);
13 | this.state = {
14 | labels: []
15 | }
16 | }
17 |
18 | componentDidMount() {
19 | this.getLabels();
20 | }
21 |
22 | getLabels() {
23 | console.log('LabelTags@getLabels');
24 | let url = API.get_labels;
25 | asyncFetch('GET', url, {},
26 | (res) => {
27 | if (res.code === 0) {
28 | let labels = [];
29 | res.data.map(function(row) {
30 | labels.push({
31 | key: row.id,
32 | name: row.name
33 | });
34 | return 0;
35 | });
36 | this.setState({
37 | labels: labels
38 | });
39 | } else {
40 | message.error(res.message);
41 | }
42 | }, {}, 'cors');
43 | }
44 |
45 | handleOnAdd = (value) => {
46 | let url = API.create_labels;
47 | let params = {
48 | name: value
49 | };
50 | asyncFetch('POST', url, params,
51 | (res) => {
52 | console.log(res);
53 | if (res.code === 0) {
54 | message.success("add label success.");
55 | let newLabelId = res.data;
56 | const labels = _.clone(this.state.labels);
57 | labels.push({key: newLabelId, name: value});
58 | this.setState({labels: labels});
59 | } else {
60 | message.error(res.message);
61 | }
62 | }, {}, 'cors');
63 | };
64 |
65 | render() {
66 |
67 | const { Search } = Input;
68 |
69 | return (
70 |
71 |
72 | this.handleOnAdd(value)}
75 | enterButton="Add"
76 | style={{marginTop: "6px", marginBottom: "8px"}}
77 | />
78 |
79 | {
80 | this.state.labels.map(label => (
81 | {label.name}
82 | ))
83 | }
84 |
85 | );
86 | }
87 | }
88 |
89 | export default LabelTags;
--------------------------------------------------------------------------------
/src/components/Marker/MarkerCreate/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { API } from "../../../config/api.config";
3 | import { Input, message } from "antd";
4 | import asyncFetch from "../../../utils/asyncFetch";
5 | import CheckboxLabel from "../../Label/CheckboxLabel/index";
6 | import moment from "moment";
7 |
8 | const DATE_FORMAT = 'yyyy-MM-dd hh:mm:ss';
9 |
10 | class MarkerCreate extends React.Component {
11 |
12 | constructor(props) {
13 | super(props);
14 | this.state = {
15 | content: "",
16 | checkLabelIds: []
17 | };
18 | }
19 |
20 | handleOnSearch = (value) => {
21 | let url = API.post_create;
22 | let labelIds = [];
23 | this.state.checkLabelIds.map(function(id) {
24 | labelIds.push({
25 | id: id
26 | });
27 | return 0;
28 | });
29 | let now = moment().format('YYYY-MM-DD HH:mm:ss');
30 | let params = {
31 | title: value,
32 | content: this.state.content,
33 | labels: labelIds,
34 | users: [{id: 1}], // todo: 获取当前登陆用户的id,
35 | update_date: now
36 | };
37 | asyncFetch('POST', url, params,
38 | (res) => {
39 | console.log(res);
40 | if (res.code === 0) {
41 | message.success("create success.");
42 | } else {
43 | message.error(res.message);
44 | }
45 | }, {}, 'cors');
46 | };
47 |
48 | handleTextAreaOnChange = (e) => {
49 | this.setState({
50 | content: e.target.value
51 | });
52 | };
53 |
54 | handleCheckboxOnChange = (values) => {
55 | console.log("handleCheckboxOnChange");
56 | console.log(values);
57 | this.setState({
58 | checkLabelIds: values
59 | });
60 | };
61 |
62 | render() {
63 | const { TextArea } = Input;
64 | const { Search } = Input;
65 | return (
66 |
67 |
68 | this.handleOnSearch(value)}
71 | enterButton="Submit"
72 | />
73 |
74 |
80 |
81 |
84 |
85 |
86 | );
87 | }
88 |
89 | }
90 |
91 | export default MarkerCreate;
--------------------------------------------------------------------------------
/src/components/Marker/MarkerList/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Table, Divider, Popconfirm, message } from "antd";
3 | import ModalView from "../ModalView/index";
4 | import Columns from "../../../config/list.config";
5 | import IconLike from "../../common/IconLike/index";
6 | import asyncFetch from "../../../utils/asyncFetch";
7 | import { API } from "../../../config/api.config";
8 | import _ from "lodash";
9 | import PropTypes from "prop-types";
10 |
11 | class MarkerList extends React.Component {
12 |
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | dataSource: [],
17 | modalViewVisible: false,
18 | modalViewTitle: "",
19 | modalViewRecord: {}
20 | };
21 | }
22 |
23 | componentDidMount() {
24 | console.log("componentDidMount");
25 | this.getMarkers();
26 | }
27 |
28 | onView = (record) => {
29 | console.log("onView");
30 | console.log(record);
31 | this.setState({
32 | modalViewVisible: true,
33 | modalViewTitle: record.title,
34 | modalViewRecord: record
35 | });
36 | };
37 |
38 | handleModalViewOnOk() {
39 | console.log("handleModalViewOnOk");
40 | this.setState({modalViewVisible: false});
41 | }
42 |
43 | handleModalViewOnCancel() {
44 | console.log("handleModalViewOnCancel");
45 | this.setState({modalViewVisible: false});
46 | }
47 |
48 | getMarkers() {
49 | console.log('MarkerList@getMarkers');
50 | let url = API.get_list;
51 | asyncFetch('GET', url, {},
52 | (res) => {
53 | if (res.code === 0) {
54 | let markers = [];
55 | res.data.map(function(row) {
56 | let labels = [];
57 | row.labels.map(function(label) {
58 | labels.push(label.name);
59 | return 0;
60 | });
61 | let author = 'unknown';
62 | // if (row.users.length > 0) {
63 | // author = row.users[0]['display'];
64 | // }
65 | markers.push({
66 | key: row.id,
67 | title: row.title,
68 | content: row.content,
69 | author: author,
70 | update_date: row.updateDate,
71 | label: labels.join(', ')
72 | });
73 | return 0;
74 | });
75 | this.setState({
76 | dataSource: markers
77 | });
78 | } else {
79 | message.error(res.message);
80 | }
81 | }, {}, 'cors');
82 | }
83 |
84 | deleteMarker(record) {
85 | let url = API.delete_marker.replace("%s", record.key);
86 | asyncFetch('DELETE', url, {},
87 | (res) => {
88 | if (res.code === 0) {
89 | message.success('delete success.');
90 | this.getMarkers();
91 | } else {
92 | message.error(res.message);
93 | }
94 | }, {}, 'cors');
95 | }
96 |
97 | render() {
98 |
99 | const columns = _.clone(Columns);
100 | columns.push({
101 | title: 'Operate',
102 | key: 'action',
103 | width: '18%',
104 | render: (text, record) => (
105 |
106 | this.onView(record)}>View
107 |
108 | this.onEdit(record)}>Edit
109 |
110 |
111 | this.deleteMarker(record)}
114 | okText="Yes"
115 | cancelText="No">
116 | Delete
117 |
118 |
119 |
120 |
121 | ),
122 | });
123 |
124 | return (
125 |
126 |
130 |
this.handleModalViewOnOk()}
135 | handleOnCancel={() => this.handleModalViewOnCancel()}
136 | />
137 |
138 | );
139 | }
140 |
141 | }
142 |
143 | MarkerList.contextTypes = {
144 | token: PropTypes.string
145 | };
146 |
147 | export default MarkerList;
--------------------------------------------------------------------------------
/src/components/Marker/ModalView/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Modal } from "antd";
3 |
4 | class ModalView extends React.Component {
5 |
6 | constructor(props) {
7 | super(props);
8 | this.state = {
9 | visible: props.visible,
10 | title: props.title,
11 | record: props.record
12 | };
13 | }
14 |
15 | componentWillReceiveProps(props) {
16 | console.log("ModalView@componentWillReceiveProps");
17 | console.log(props);
18 | this.setState({
19 | visible: props.visible,
20 | title: props.title,
21 | record: props.record
22 | });
23 | }
24 |
25 | handleOk = (e) => {
26 | this.setState({visible: false});
27 | this.props.handleOnOk();
28 | };
29 |
30 | handleCancel = (e) => {
31 | this.setState({visible: false});
32 | this.props.handleOnCancel();
33 | };
34 |
35 | render() {
36 | return (
37 |
47 | {this.state.record.content}
48 |
49 | );
50 | }
51 |
52 | }
53 |
54 | export default ModalView;
--------------------------------------------------------------------------------
/src/components/Statistic/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Statistic, Row, Col, message } from 'antd';
3 | import { API } from "../../config/api.config";
4 | import asyncFetch from "../../utils/asyncFetch";
5 |
6 | class SystemStatistic extends React.Component {
7 |
8 | constructor(props) {
9 | super(props);
10 | this.state = {
11 | statistic: {}
12 | };
13 | }
14 |
15 | componentDidMount() {
16 | this.getStatistic();
17 | }
18 |
19 | getStatistic() {
20 | let url = API.get_statistic;
21 | asyncFetch('GET', url, {},
22 | (res) => {
23 | if (res.code === 0) {
24 | this.setState({
25 | statistic: res.data
26 | });
27 | } else {
28 | message.error(res.message);
29 | }
30 | }, {}, 'cors');
31 | }
32 |
33 | render() {
34 | return (
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | );
47 | }
48 | }
49 |
50 | export default SystemStatistic;
--------------------------------------------------------------------------------
/src/components/common/HighOrderComponents/AsyncComponent.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 |
3 | export default function asyncComponent(importComponent) {
4 | class AsyncComponent extends Component {
5 | constructor(props) {
6 | super(props);
7 | this.state = {
8 | component: null
9 | };
10 | }
11 |
12 | async componentDidMount() {
13 | const { default: component } = await importComponent();
14 | this.setState({
15 | component: component
16 | });
17 | }
18 |
19 | render() {
20 | const Component = this.state.component;
21 | return Component ? : null;
22 | }
23 | }
24 |
25 | return AsyncComponent;
26 | }
--------------------------------------------------------------------------------
/src/components/common/IconLike/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Icon } from "antd";
3 |
4 | class IconLike extends React.Component {
5 |
6 | constructor(props) {
7 | super(props);
8 | this.state = {
9 | type: "heart-o",
10 | color: ""
11 | };
12 | }
13 |
14 | handleOnClick = () => {
15 | let type = "heart";
16 | let color = "#1890ff";
17 | if (this.state.type === "heart") {
18 | type = "heart-o";
19 | color = ""
20 | }
21 | this.setState({
22 | type: type,
23 | color: color
24 | });
25 | };
26 |
27 | render() {
28 | return (
29 |
30 | );
31 | }
32 |
33 | }
34 |
35 | export default IconLike;
--------------------------------------------------------------------------------
/src/components/common/LoginForm/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {withRouter} from 'react-router-dom'
3 | import PropTypes from 'prop-types'
4 | import './index.less'
5 | import {Form, Icon, Input, Button, Checkbox, message} from 'antd';
6 | import asyncFetch from "../../../utils/asyncFetch";
7 | // import {AppApis} from "../../config/api.config";
8 | // import {ROLES_DEFAULT_PAGE} from "../../config/router.config";
9 | import {API} from "../../../config/api.config";
10 |
11 | const FormItem = Form.Item;
12 |
13 | class LoginForm extends React.Component {
14 | constructor(props) {
15 | super(props);
16 | this.state = {
17 | loginError: false,
18 | loginErrorMsg: '',
19 | loading: false,
20 | login: false,
21 | userInfo: {}
22 | }
23 | }
24 |
25 | handleSubmit = (e) => {
26 | console.log("LoginForm@handleSubmit");
27 | e.preventDefault();
28 | this.props.form.validateFields((err, values) => {
29 | if (!err) {
30 | console.log(values);
31 | let url = API.get_login;
32 | let params = {
33 | userName: values.userName,
34 | password: values.password
35 | };
36 | asyncFetch('POST', url, params,
37 | (res) => {
38 | console.log(res.headers.get("authorization"));
39 | let token = res.headers.get("authorization");
40 | if (res.status == 200) {
41 | message.success("login success.");
42 | this.getUserInfo(token);
43 | } else {
44 | message.error(res.message);
45 | }
46 | }, {}, 'cors', {}, true);
47 | }
48 | });
49 | };
50 |
51 | getUserInfo = (token) => {
52 | console.log('LoginForm@getUserInfo');
53 | let url = API.get_current_user;
54 | asyncFetch('GET', url, {},
55 | (res) => {
56 | console.log(res);
57 | if (res.code === 0) {
58 | message.success("current user.");
59 | setTimeout(() => {
60 | this.context.setLoginInfo(true, res.data, token);
61 | this.props.history.push({
62 | pathname: '/home'
63 | })
64 | }, 1000)
65 | } else {
66 | message.error(res.message);
67 | }
68 | }, {}, 'cors', {}, false, token);
69 | };
70 |
71 | render() {
72 |
73 | const {getFieldDecorator} = this.props.form;
74 |
75 | return (
76 |
108 | )
109 | }
110 | }
111 |
112 | LoginForm.contextTypes = {
113 | setLoginInfo: PropTypes.func,
114 | loginType: PropTypes.number
115 | };
116 |
117 | export default withRouter(Form.create()(LoginForm));
--------------------------------------------------------------------------------
/src/components/common/LoginForm/index.less:
--------------------------------------------------------------------------------
1 | .login-form-button {
2 | width: 100%;
3 | display: block;
4 | }
5 |
6 | .login-card {
7 | position: absolute;
8 | top: 50%;
9 | left: 50%;
10 | width: 500px;
11 | padding: 20px;
12 | background: #393;
13 | color: #fff;
14 | transform: translate(-50%, -50%);
15 | }
--------------------------------------------------------------------------------
/src/components/common/UserAvatar/index.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Popover, Avatar} from "antd";
3 | import getColor from "../../../utils/common";
4 | import PropTypes from "prop-types";
5 |
6 | class UserAvatar extends React.Component {
7 |
8 | render() {
9 |
10 | let avatarColor = getColor();
11 |
12 | return (
13 | Logout}
16 | trigger="click">
17 |
25 | {this.context.userInfo['display']}
26 |
27 |
28 | );
29 | }
30 |
31 | }
32 |
33 | UserAvatar.contextTypes = {
34 | userInfo: PropTypes.object
35 | };
36 |
37 | export default UserAvatar;
--------------------------------------------------------------------------------
/src/config/api.config.js:
--------------------------------------------------------------------------------
1 | const API = {
2 | 'get_list': '/marker',
3 | 'post_create': '/marker',
4 | 'delete_marker': '/marker/%s',
5 |
6 |
7 | 'get_labels': '/label',
8 | 'create_labels': '/label',
9 |
10 | 'get_login': '/login',
11 | 'get_current_user': '/user/current_user',
12 |
13 | 'get_statistic': '/statistic',
14 | };
15 |
16 | export { API };
--------------------------------------------------------------------------------
/src/config/list.config.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Tag } from 'antd';
3 | import getColor from "../utils/common";
4 |
5 | const Columns = [
6 | {
7 | title: "Title",
8 | dataIndex: "title",
9 | key: "title",
10 | width: "16%"
11 | }, {
12 | title: "Content",
13 | dataIndex: "content",
14 | key: "content"
15 | }, {
16 | title: "Author",
17 | dataIndex: "author",
18 | key: "author",
19 | width: "9%"
20 | }, {
21 | title: "Label",
22 | dataIndex: "label",
23 | key: "label",
24 | width: "7%",
25 | render: (text) => ({text})
26 | }, {
27 | title: "Update Date",
28 | dataIndex: "update_date",
29 | key: "update_date",
30 | width: "15%"
31 | }
32 | ];
33 |
34 | export default Columns;
--------------------------------------------------------------------------------
/src/config/marker.config.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PoldiChen/marker-react/bc0b635351571c4d6b9e32ae1ab636d76d7c90df/src/config/marker.config.js
--------------------------------------------------------------------------------
/src/config/routes.config.js:
--------------------------------------------------------------------------------
1 | import AsyncCompnent from "../components/common/HighOrderComponents/AsyncComponent";
2 |
3 | const PageHome = AsyncCompnent(() => import("../pages/PageHome.jsx"));
4 | const PageList = AsyncCompnent(() => import("../pages/PageList.jsx"));
5 | const PageMarker = AsyncCompnent(() => import("../pages/PageMarker.jsx"));
6 | const PageLabel = AsyncCompnent(() => import("../pages/PageLabel.jsx"));
7 | const PageSetting = AsyncCompnent(() => import("../pages/PageSetting.jsx"));
8 |
9 | const ROUTES = [
10 | {
11 | key: 'Home',
12 | link: '/home',
13 | iconType: 'home',
14 | text: 'Home',
15 | component: PageHome
16 | }, {
17 | key: 'List',
18 | link: '/list',
19 | iconType: 'profile',
20 | text: 'List',
21 | component: PageList
22 | }, {
23 | key: 'Marker',
24 | link: '/marker',
25 | iconType: 'edit',
26 | text: 'Marker',
27 | component: PageMarker
28 | }, {
29 | key: 'Label',
30 | link: '/label',
31 | iconType: 'tag',
32 | text: 'Label',
33 | component: PageLabel
34 | }, {
35 | key: 'Setting',
36 | link: '/setting',
37 | iconType: 'setting',
38 | text: 'Setting',
39 | component: PageSetting
40 | }
41 | ];
42 |
43 | export { ROUTES };
--------------------------------------------------------------------------------
/src/css/main.less:
--------------------------------------------------------------------------------
1 | @full-height: 100%;
2 |
3 | html, body, #root, .full-height {
4 | height: @full-height;
5 | }
6 |
7 | .login-card {
8 | position: absolute;
9 | top: 50%;
10 | left: 50%;
11 | width: 500px;
12 | padding: 20px;
13 | background: #393;
14 | color: #fff;
15 | transform: translate(-50%, -50%);
16 | }
17 |
18 | .login-loading {
19 | position: absolute;
20 | top: 50%;
21 | left: 50%;
22 | width: 200px;
23 | padding: 20px;
24 | text-align: center;
25 | transform: translate(-50%, -50%);
26 | }
27 |
28 | // 表格奇数行样式
29 | .trOdd {}
30 |
31 | // 表格偶数行样式
32 | .trEven {
33 | background: #f1f1f1;
34 | }
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: sans-serif;
5 | }
6 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import './index.css';
4 | import App from './App';
5 | import registerServiceWorker from './registerServiceWorker';
6 |
7 | ReactDOM.render(, document.getElementById('root'));
8 | registerServiceWorker();
9 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/pages/Layouts.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import { Layout, Breadcrumb } from 'antd';
4 | import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
5 | import 'antd/dist/antd.css';
6 | import Menus from "./Menus";
7 | import { ROUTES } from "../config/routes.config";
8 | import _ from "lodash";
9 |
10 | const routes = _.clone(ROUTES);
11 |
12 | class Layouts extends React.Component {
13 |
14 | constructor(props) {
15 | super(props);
16 | this.state = {
17 | current: ""
18 | }
19 | }
20 |
21 | updateActive = (key) => {
22 | let index = _.findIndex(routes, route => route.key === key);
23 | this.setState({current: routes[index]['text']});
24 | };
25 |
26 | render() {
27 |
28 | const { Header, Content, Footer } = Layout;
29 |
30 | return(
31 |
32 |
33 |
39 |
40 |
41 | Index
42 | {this.state.current}
43 |
44 |
45 | {
46 | routes.map((route) =>
47 | )
48 | }
49 |
50 |
51 |
52 |
53 |
54 | );
55 | }
56 |
57 | }
58 |
59 | Layouts.contextTypes = {
60 | login: PropTypes.bool,
61 | userInfo: PropTypes.object,
62 | setLoginInfo: PropTypes.func
63 | };
64 |
65 | export default Layouts;
--------------------------------------------------------------------------------
/src/pages/Menus.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import { Menu, Icon } from "antd";
4 | import { Link } from 'react-router-dom';
5 | import { ROUTES } from "../config/routes.config";
6 | import _ from "lodash";
7 | import UserAvatar from "../components/common/UserAvatar";
8 |
9 | const routes = _.clone(ROUTES);
10 |
11 | class Menus extends React.Component {
12 |
13 | constructor(props) {
14 | super(props);
15 | this.state = {
16 | activeKey: ''
17 | };
18 | }
19 |
20 | componentDidMount() {
21 | console.log("Menus.jsx@componentDidMount");
22 | let pathname = this.context.router.route.location.pathname;
23 | console.log(pathname);
24 | let index = _.findIndex(routes, route => route.link === pathname);
25 | console.log(index);
26 | if (index === -1) {
27 | index = 0;
28 | }
29 | this.setState({activeKey: routes[index]['key']});
30 | this.props.updateActive(routes[index]['key']);
31 | }
32 |
33 | handleClick = (e) => {
34 | this.setState({activeKey: e.key});
35 | this.props.updateActive(e.key);
36 | };
37 |
38 | render() {
39 |
40 | return (
41 |
57 | );
58 | }
59 | }
60 |
61 | Menus.contextTypes = {
62 | router: PropTypes.object
63 | };
64 |
65 | export default Menus;
--------------------------------------------------------------------------------
/src/pages/PageHome.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import SystemStatistic from "../components/Statistic";
3 |
4 | class PageHome extends React.Component {
5 |
6 | render() {
7 | return(
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | Marker, mark down everything.
17 | Build with React, Ant Design, Spring Boot. ->
18 |
GitHub
19 |
20 | );
21 | }
22 |
23 | }
24 |
25 | export default PageHome;
--------------------------------------------------------------------------------
/src/pages/PageLabel.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import LabelTags from "../components/Label/LabelTags/index";
3 |
4 | class PageLabel extends React.Component {
5 |
6 | render() {
7 | return(
8 |
9 | );
10 | }
11 |
12 | }
13 |
14 | export default PageLabel;
--------------------------------------------------------------------------------
/src/pages/PageList.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import MarkerList from "../components/Marker/MarkerList/index";
3 |
4 | class PageList extends React.Component {
5 |
6 | constructor(props) {
7 | super(props);
8 | this.state = {};
9 | }
10 |
11 | render() {
12 | return(
13 |
14 | );
15 | }
16 | }
17 |
18 | export default PageList;
--------------------------------------------------------------------------------
/src/pages/PageLogin.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import PropTypes from "prop-types";
3 | import LoginForm from "../components/common/LoginForm/index";
4 | import { BrowserRouter as Router } from 'react-router-dom'
5 | import { Card, Icon } from 'antd'
6 | import "../css/main.less";
7 |
8 | class PageLogin extends React.Component {
9 |
10 | constructor(props) {
11 | super(props);
12 | }
13 |
14 | render() {
15 | return (
16 |
17 |
18 |
19 |
20 | Marker
21 |
22 |
23 |
24 |
25 | );
26 | }
27 | }
28 |
29 | PageLogin.contextTypes = {
30 | login: PropTypes.bool,
31 | userInfo: PropTypes.object,
32 | setLoginInfo: PropTypes.func
33 | };
34 |
35 | export default PageLogin;
--------------------------------------------------------------------------------
/src/pages/PageMarker.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import MarkerCreate from "../components/Marker/MarkerCreate/index";
3 |
4 | class PageMarker extends React.Component {
5 |
6 | constructor(props) {
7 | super(props);
8 | this.state = {};
9 | }
10 |
11 | render() {
12 | return(
13 |
14 | );
15 | }
16 |
17 | }
18 |
19 | export default PageMarker;
--------------------------------------------------------------------------------
/src/pages/PageSetting.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | class PageSetting extends React.Component {
4 |
5 | render() {
6 | return(
7 | setting page.
8 | );
9 | }
10 |
11 | }
12 |
13 | export default PageSetting;
--------------------------------------------------------------------------------
/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (isLocalhost) {
36 | // This is running on localhost. Lets check if a service worker still exists or not.
37 | checkValidServiceWorker(swUrl);
38 |
39 | // Add some additional logging to localhost, pointing developers to the
40 | // service worker/PWA documentation.
41 | navigator.serviceWorker.ready.then(() => {
42 | console.log(
43 | 'This web app is being served cache-first by a service ' +
44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ'
45 | );
46 | });
47 | } else {
48 | // Is not local host. Just register service worker
49 | registerValidSW(swUrl);
50 | }
51 | });
52 | }
53 | }
54 |
55 | function registerValidSW(swUrl) {
56 | navigator.serviceWorker
57 | .register(swUrl)
58 | .then(registration => {
59 | registration.onupdatefound = () => {
60 | const installingWorker = registration.installing;
61 | installingWorker.onstatechange = () => {
62 | if (installingWorker.state === 'installed') {
63 | if (navigator.serviceWorker.controller) {
64 | // At this point, the old content will have been purged and
65 | // the fresh content will have been added to the cache.
66 | // It's the perfect time to display a "New content is
67 | // available; please refresh." message in your web app.
68 | console.log('New content is available; please refresh.');
69 | } else {
70 | // At this point, everything has been precached.
71 | // It's the perfect time to display a
72 | // "Content is cached for offline use." message.
73 | console.log('Content is cached for offline use.');
74 | }
75 | }
76 | };
77 | };
78 | })
79 | .catch(error => {
80 | console.error('Error during service worker registration:', error);
81 | });
82 | }
83 |
84 | function checkValidServiceWorker(swUrl) {
85 | // Check if the service worker can be found. If it can't reload the page.
86 | fetch(swUrl)
87 | .then(response => {
88 | // Ensure service worker exists, and that we really are getting a JS file.
89 | if (
90 | response.status === 404 ||
91 | response.headers.get('content-type').indexOf('javascript') === -1
92 | ) {
93 | // No service worker found. Probably a different app. Reload the page.
94 | navigator.serviceWorker.ready.then(registration => {
95 | registration.unregister().then(() => {
96 | window.location.reload();
97 | });
98 | });
99 | } else {
100 | // Service worker found. Proceed as normal.
101 | registerValidSW(swUrl);
102 | }
103 | })
104 | .catch(() => {
105 | console.log(
106 | 'No internet connection found. App is running in offline mode.'
107 | );
108 | });
109 | }
110 |
111 | export function unregister() {
112 | if ('serviceWorker' in navigator) {
113 | navigator.serviceWorker.ready.then(registration => {
114 | registration.unregister();
115 | });
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/utils/asyncFetch.js:
--------------------------------------------------------------------------------
1 | import {message} from 'antd';
2 |
3 | const defaultFailSolution = (res) => {
4 | if(typeof res !== 'object') {
5 | message.warn(res);
6 | }
7 | };
8 |
9 | /**
10 | * 异步获取数据方法
11 | * @param method 'GET' 'POST' 'HEAD' 'PUT' 'DELETE'等,必填
12 | * @param API api.config.js中定义的常量名,必填
13 | * @param otherParams 参数,在get 和 head方法时会自动拼接到url中,必填-如果没有设为{}
14 | * @param success 成功处理函数,必填,参数为res
15 | * @param otherHeader 其他的头部配置 比如content-type等
16 | * @param corsSetting 跨域设置,默认为same-origin 严格不跨域,可选值为cors,no-cors. same-origin
17 | * same-origin:该模式是不允许跨域的,它需要遵守同源策略,否则浏览器会返回一个error告知不能跨域;其对应的response type为basic。
18 | * cors: 该模式支持跨域请求,顾名思义它是以CORS的形式跨域;当然该模式也可以同域请求不需要后端额外的CORS支持;
19 | * no-cors: 该模式用于跨域请求但是服务器不带CORS响应头,也就是服务端不支持CORS;其对应的response type为opaque。
20 | * @param fail 失败处理函数,默认为defaultFailSolution,效果为将message填充到消息通知栏。参数为msg和http code,自行处理303的状态
21 | */
22 | const asyncFetch = (method, API, otherParams, success, otherHeader = {},
23 | corsSetting = 'same-origin', fail = defaultFailSolution, rawResp = false, token = "") => {
24 | console.log("asyncFetch");
25 | console.log(token);
26 | let api = process.env.NODE_ENV === 'development' ? API : '/cross_origin' + API;
27 | let header = new Headers();
28 | header.set('Content-Type', 'application/json');
29 | header.set("Authorization", token);
30 | for (let key in otherHeader) {
31 | if (otherHeader.hasOwnProperty(key)) {
32 | header.set(key, otherHeader[key])
33 | }
34 | }
35 | let init = {
36 | method: method.toUpperCase(),
37 | headers: header,
38 | mode: corsSetting,
39 | credentials: 'include'
40 | };
41 |
42 | if (method === 'GET' || method === 'HEAD') {
43 | let urlSearch = [];
44 | for (let key in otherParams) {
45 | if (otherParams.hasOwnProperty(key)) {
46 | urlSearch.push(key + '=' + otherParams[key])
47 | }
48 | }
49 | api += urlSearch.length === 0 ? '' : ('?' + urlSearch.join('&'))
50 | } else {
51 | init['body'] = JSON.stringify(otherParams)
52 | }
53 | fetch(api, init).then((response) => {
54 |
55 | if (rawResp) {
56 | success(response);
57 | return;
58 | }
59 |
60 | console.log(response.headers.get("authorization"));
61 |
62 | if (response.status !== 200) {
63 | response.text().then(text => {
64 | fail(text);
65 | });
66 | return;
67 | }
68 | try {
69 | response.json().then((res) => {
70 | success(res)
71 | });
72 | } catch (err) {
73 | fail(err.message)
74 | }
75 | }, (error) => {
76 | fail('获取数据出错,请刷新页面')
77 | }).catch(function (err) {
78 | fail(err.message);
79 | });
80 | };
81 |
82 | export default asyncFetch;
--------------------------------------------------------------------------------
/src/utils/common.js:
--------------------------------------------------------------------------------
1 |
2 | const colors = ["red", "volcano", "orange", "gold", "yellow", "lime", "green", "cyan", "blue", "geekblue", "purple", "magenta"];
3 |
4 | const getRandom = (min, max) => {
5 | return Math.floor(Math.random() * (max - min + 1 ) + min);
6 | };
7 |
8 | const getColor = () => {
9 | let index = getRandom(0, colors.length - 1);
10 | return colors[index];
11 | };
12 |
13 | export default getColor;
--------------------------------------------------------------------------------