diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 0000000..6c9a860
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,45 @@
+module.exports = {
+ parser: 'babel-eslint',
+ env: {
+ browser: true,
+ es6: true,
+ },
+ extends: ['airbnb', 'airbnb/hooks', 'prettier', 'prettier/react', 'plugin:prettier/recommended'],
+ globals: {
+ Atomics: 'readonly',
+ SharedArrayBuffer: 'readonly',
+ },
+ parserOptions: {
+ ecmaFeatures: {
+ jsx: true,
+ legacyDecorators: true,
+ },
+ ecmaVersion: 2018,
+ sourceType: 'module',
+ },
+ plugins: ['react', 'prettier'],
+ rules: {
+ 'import/extensions': 0,
+ 'prettier/prettier': 'error',
+ 'react/jsx-filename-extension': [1, { extensions: ['.js', '.jsx'] }],
+
+ /* 确定不会发生改变的规则 */
+ 'react/jsx-props-no-spreading': 0, // 允许 jsx 属性使用扩展运算符
+ 'react/prop-types': 0, // 不需要使用 prop-types 检查组件 props
+ 'import/prefer-default-export': 0, // 不需要优先使用默认导出
+ 'import/no-unresolved': 0,
+ 'react/no-array-index-key': 0,
+ /* 可能会改变的规则 */
+ 'react/react-in-jsx-scope': 0, // 使用 jsx 的组件无需引入 React
+ // 移动端大部分不需要键盘事件
+ 'jsx-a11y/click-events-have-key-events': 0,
+ // 无障碍暂时用不到
+ 'jsx-a11y/no-static-element-interactions': 0,
+ // 后台返回的字段不是驼峰的
+ camelcase: 0,
+ // 这个限制太片面了,https://github.com/eslint/eslint/issues/10482
+ 'no-restricted-properties': 0,
+ // 全局方法无法使用
+ 'no-restricted-globals': 0,
+ },
+};
diff --git a/package.json b/package.json
index f6c7705..58daa27 100755
--- a/package.json
+++ b/package.json
@@ -11,14 +11,29 @@
"dependencies": {
"classnames": "^2.2.6",
"lodash": "^4.17.15",
+ "node-sass": "^4.14.1",
"prop-types": "^15.7.2",
"react": "^16.12.0",
"react-dom": "^16.12.0",
"react-redux": "^7.2.0",
- "redux": "^4.0.5"
+ "redux": "^4.0.5",
+ "eslint": "^6.7.1",
+ "eslint-config-airbnb": "^18.0.1",
+ "eslint-config-prettier": "^6.7.0",
+ "eslint-plugin-import": "^2.18.2",
+ "eslint-plugin-jsx-a11y": "^6.2.3",
+ "eslint-plugin-prettier": "^3.1.1",
+ "eslint-plugin-react": "^7.16.0",
+ "eslint-plugin-react-hooks": "^1.7.0",
+ "prettier": "^1.19.1"
},
"devDependencies": {
"react-scripts": "3.4.0"
},
- "browserslist": [">0.2%", "not dead", "not ie <= 11", "not op_mini all"]
+ "browserslist": [
+ ">0.2%",
+ "not dead",
+ "not ie <= 11",
+ "not op_mini all"
+ ]
}
diff --git a/src/actions/PlayersActions.js b/src/actions/PlayersActions.js
index 8f427c2..40108f2 100755
--- a/src/actions/PlayersActions.js
+++ b/src/actions/PlayersActions.js
@@ -1,22 +1,30 @@
-import * as types from '../constants/ActionTypes';
+import * as types from "../constants/ActionTypes";
-export function addPlayer(name) {
+export function addPlayer(name, optionId) {
return {
type: types.ADD_PLAYER,
name,
+ optionId
};
}
export function deletePlayer(id) {
return {
type: types.DELETE_PLAYER,
- id,
+ id
};
}
export function starPlayer(id) {
return {
type: types.STAR_PLAYER,
- id,
+ id
+ };
+}
+
+export function changePage(current) {
+ return {
+ type: types.CHANGE_PAGE,
+ current
};
}
diff --git a/src/components/AddPlayerInput.js b/src/components/AddPlayerInput.js
index 5d914d8..0dd684e 100755
--- a/src/components/AddPlayerInput.js
+++ b/src/components/AddPlayerInput.js
@@ -1,27 +1,16 @@
-import React, { Component } from 'react';
-import classnames from 'classnames';
-import PropTypes from 'prop-types';
-import styles from './AddPlayerInput.css';
+import React, { Component } from "react";
+import classnames from "classnames";
+import PropTypes from "prop-types";
+import OptionSelect from "./OptionSelect/index.js";
+import styles from "./AddPlayerInput.css";
class AddPlayerInput extends Component {
- render() {
- return (
-
- );
- }
-
constructor(props, context) {
super(props, context);
this.state = {
- name: this.props.name || '',
+ // eslint-disable-next-line react/destructuring-assignment
+ name: this.props.name || "",
+ optionId: 0
};
}
@@ -31,15 +20,42 @@ class AddPlayerInput extends Component {
handleSubmit(e) {
const name = e.target.value.trim();
+ const { optionId } = this.state;
if (e.which === 13) {
- this.props.addPlayer(name);
- this.setState({ name: '' });
+ // eslint-disable-next-line react/destructuring-assignment
+ this.props.addPlayer(name, optionId);
+ this.setState({ name: "" });
}
}
+
+ handleSelectChange(optionId) {
+ this.setState({ optionId });
+ }
+
+ render() {
+ const { positionList } = this.props;
+ const { name } = this.state;
+ return (
+
+ this.handleSelectChange(e)}
+ />
+
+
+ );
+ }
}
AddPlayerInput.propTypes = {
- addPlayer: PropTypes.func.isRequired,
+ addPlayer: PropTypes.func.isRequired
};
export default AddPlayerInput;
diff --git a/src/components/OptionSelect/index.js b/src/components/OptionSelect/index.js
new file mode 100644
index 0000000..b921d00
--- /dev/null
+++ b/src/components/OptionSelect/index.js
@@ -0,0 +1,23 @@
+import React from "react";
+
+function OptionSelect(props) {
+ const { positionList, onSelectChange } = props;
+
+ const onChange = e => {
+ onSelectChange(e.target.value.trim());
+ };
+
+ return (
+
+ );
+}
+
+export default OptionSelect;
diff --git a/src/components/PageList/index.css b/src/components/PageList/index.css
new file mode 100644
index 0000000..b3ed095
--- /dev/null
+++ b/src/components/PageList/index.css
@@ -0,0 +1,30 @@
+.pageList{
+ padding-left: 0;
+ margin-bottom: 0;
+ list-style: none;
+ }
+
+.btnAction{
+ width: 20px;
+ height: 20px;
+ background: white;
+ text-align: center;
+ float: left;
+ margin-left: 5px;
+ cursor: pointer;
+ border: 1px solid #5c75b0;
+ }
+.active{
+ color: white;
+ background-color: #5c75b0;
+}
+.btnAction:focus,
+.btnAction:hover {
+ color: white;
+ background-color: #5c75b0;
+}
+
+ul {
+ list-style-type: none;
+ padding: 0;
+}
diff --git a/src/components/PageList/index.js b/src/components/PageList/index.js
new file mode 100644
index 0000000..265b4ed
--- /dev/null
+++ b/src/components/PageList/index.js
@@ -0,0 +1,32 @@
+import React from "react";
+import classNames from "classnames";
+// eslint-disable-next-line no-unused-vars
+import styles from "./index.css";
+
+function PageList(props) {
+ const { total, changePage, current } = props;
+ const totalPageList = [];
+ for (let i = 0; i < total; i += 1) {
+ totalPageList.push({ pageId: i + 1 });
+ }
+ return (
+
+ {totalPageList.map((val, index) => {
+ return (
+ // eslint-disable-next-line jsx-a11y/no-noninteractive-element-interactions
+ - changePage(index)}
+ className={classNames("btnAction", {
+ active: current === index
+ })}
+ >
+ {val.pageId}
+
+ );
+ })}
+
+ );
+}
+
+export default PageList;
diff --git a/src/components/PlayerList.js b/src/components/PlayerList.js
index 7b40246..275621a 100755
--- a/src/components/PlayerList.js
+++ b/src/components/PlayerList.js
@@ -1,13 +1,16 @@
-import React, { Component } from 'react';
-import PropTypes from 'prop-types';
-import styles from './PlayerList.css';
-import PlayerListItem from './PlayerListItem';
+/* eslint-disable react/forbid-prop-types */
+/* eslint-disable react/prefer-stateless-function */
+import React, { Component } from "react";
+import PropTypes from "prop-types";
+import styles from "./PlayerList.css";
+import PlayerListItem from "./PlayerListItem";
class PlayerList extends Component {
render() {
+ const { players, actions } = this.props;
return (
- {this.props.players.map((player, index) => {
+ {players.map((player, index) => {
return (
);
})}
@@ -27,7 +30,7 @@ class PlayerList extends Component {
PlayerList.propTypes = {
players: PropTypes.array.isRequired,
- actions: PropTypes.object.isRequired,
+ actions: PropTypes.object.isRequired
};
export default PlayerList;
diff --git a/src/components/PlayerListItem.js b/src/components/PlayerListItem.js
index ec9758c..7f3a21b 100755
--- a/src/components/PlayerListItem.js
+++ b/src/components/PlayerListItem.js
@@ -1,7 +1,11 @@
-import React, { Component } from 'react';
-import classnames from 'classnames';
-import PropTypes from 'prop-types';
-import styles from './PlayerListItem.css';
+/* eslint-disable react/require-default-props */
+/* eslint-disable react/destructuring-assignment */
+/* eslint-disable react/button-has-type */
+/* eslint-disable react/prefer-stateless-function */
+import React, { Component } from "react";
+import classnames from "classnames";
+import PropTypes from "prop-types";
+import styles from "./PlayerListItem.css";
class PlayerListItem extends Component {
render() {
@@ -23,9 +27,9 @@ class PlayerListItem extends Component {
onClick={() => this.props.starPlayer(this.props.id)}
>
@@ -47,7 +51,7 @@ PlayerListItem.propTypes = {
team: PropTypes.string.isRequired,
position: PropTypes.string.isRequired,
starred: PropTypes.bool,
- starPlayer: PropTypes.func.isRequired,
+ starPlayer: PropTypes.func.isRequired
};
export default PlayerListItem;
diff --git a/src/components/index.js b/src/components/index.js
index 12ce117..253208a 100755
--- a/src/components/index.js
+++ b/src/components/index.js
@@ -1,3 +1,4 @@
-export { default as AddPlayerInput } from './AddPlayerInput';
-export { default as PlayerList } from './PlayerList';
-export { default as PlayerListItem } from './PlayerListItem';
+export { default as AddPlayerInput } from "./AddPlayerInput";
+export { default as PlayerList } from "./PlayerList";
+export { default as PageList } from "./PageList/index.js";
+export { default as PlayerListItem } from "./PlayerListItem";
diff --git a/src/constants/ActionTypes.js b/src/constants/ActionTypes.js
index b796fae..160d585 100755
--- a/src/constants/ActionTypes.js
+++ b/src/constants/ActionTypes.js
@@ -1,3 +1,4 @@
-export const ADD_PLAYER = 'ADD_PLAYER';
-export const STAR_PLAYER = 'STAR_PLAYER';
-export const DELETE_PLAYER = 'DELETE_PLAYER';
+export const ADD_PLAYER = "ADD_PLAYER";
+export const STAR_PLAYER = "STAR_PLAYER";
+export const DELETE_PLAYER = "DELETE_PLAYER";
+export const CHANGE_PAGE = "CHANGE_PAGE";
diff --git a/src/containers/PlayerListApp.js b/src/containers/PlayerListApp.js
index 0e4bfa5..bac4f98 100755
--- a/src/containers/PlayerListApp.js
+++ b/src/containers/PlayerListApp.js
@@ -1,27 +1,52 @@
-import React, { Component } from 'react';
-import styles from './PlayerListApp.css';
-import { connect } from 'react-redux';
-
-import { addPlayer, deletePlayer, starPlayer } from '../actions/PlayersActions';
-import { PlayerList, AddPlayerInput } from '../components';
+/* eslint-disable no-shadow */
+import React, { Component } from "react";
+import { connect } from "react-redux";
+import _ from "lodash";
+import styles from "./PlayerListApp.css";
+import {
+ addPlayer,
+ deletePlayer,
+ starPlayer,
+ changePage
+} from "../actions/PlayersActions";
+import { PlayerList, AddPlayerInput, PageList } from "../components";
+// eslint-disable-next-line react/prefer-stateless-function
class PlayerListApp extends Component {
render() {
const {
- playerlist: { playersById },
+ playerlist: { playersById, positionList, pageSize, current },
+ addPlayer,
+ deletePlayer,
+ starPlayer,
+ changePage
} = this.props;
-
+ const new_players = _.slice(
+ playersById,
+ current * 5,
+ current * 5 + pageSize
+ );
+ const total = playersById.length / 5;
const actions = {
- addPlayer: this.props.addPlayer,
- deletePlayer: this.props.deletePlayer,
- starPlayer: this.props.starPlayer,
+ addPlayer,
+ deletePlayer,
+ starPlayer,
+ changePage
};
return (
);
}
@@ -31,11 +56,9 @@ function mapStateToProps(state) {
return state;
}
-export default connect(
- mapStateToProps,
- {
- addPlayer,
- deletePlayer,
- starPlayer,
- },
-)(PlayerListApp);
+export default connect(mapStateToProps, {
+ addPlayer,
+ deletePlayer,
+ starPlayer,
+ changePage
+})(PlayerListApp);
diff --git a/src/reducers/playerlist.js b/src/reducers/playerlist.js
index 1bc7457..bb32257 100755
--- a/src/reducers/playerlist.js
+++ b/src/reducers/playerlist.js
@@ -1,44 +1,48 @@
-import * as types from '../constants/ActionTypes';
+/* eslint-disable no-case-declarations */
+import * as types from "../constants/ActionTypes";
const initialState = {
+ current: 0,
+ pageSize: 5,
+ positionList: [{ position: "SF" }, { position: "PG" }],
playersById: [
{
- name: 'LeBron James',
- team: 'LOS ANGELES LAKERS',
- position: 'SF',
- starred: true,
+ name: "LeBron James",
+ team: "LOS ANGELES LAKERS",
+ position: "SF",
+ starred: true
},
{
- name: 'Kevin Duran',
- team: 'GOLDEN STATE WARRIORS',
- position: 'SF',
- starred: false,
+ name: "Kevin Duran",
+ team: "GOLDEN STATE WARRIORS",
+ position: "SF",
+ starred: false
},
{
- name: 'Anthony Davis',
- team: 'NEW ORLEANS PELICANS',
- position: 'PF',
- starred: false,
+ name: "Anthony Davis",
+ team: "NEW ORLEANS PELICANS",
+ position: "PF",
+ starred: false
},
{
- name: 'Stephen Curry',
- team: 'GOLDEN STATE WARRIORS',
- position: 'PG',
- starred: false,
+ name: "Stephen Curry",
+ team: "GOLDEN STATE WARRIORS",
+ position: "PG",
+ starred: false
},
{
- name: 'James Harden',
- team: 'HOUSTON ROCKETS',
- position: 'SG',
- starred: false,
+ name: "James Harden",
+ team: "HOUSTON ROCKETS",
+ position: "SG",
+ starred: false
},
{
- name: 'Kawhi Leonard',
- team: 'TORONTO RAPTORS',
- position: 'SF',
- starred: false,
- },
- ],
+ name: "Kawhi Leonard",
+ team: "TORONTO RAPTORS",
+ position: "SF",
+ starred: false
+ }
+ ]
};
export default function players(state = initialState, action) {
@@ -50,25 +54,33 @@ export default function players(state = initialState, action) {
...state.playersById,
{
name: action.name,
- team: 'LOS ANGELES LAKERS',
- position: 'SF',
- },
- ],
+ team: "LOS ANGELES LAKERS",
+ position: state.positionList[action.optionId].position
+ }
+ ]
};
case types.DELETE_PLAYER:
return {
...state,
playersById: state.playersById.filter(
- (item, index) => index !== action.id,
- ),
+ (item, index) => index !== action.id
+ )
};
case types.STAR_PLAYER:
- let players = [...state.playersById];
- let player = players.find((item, index) => index === action.id);
+ const playerlist = [...state.playersById];
+ const player = playerlist.find(
+ (item, index) => index === action.id + state.current * state.pageSize
+ );
+
player.starred = !player.starred;
return {
...state,
- playersById: players,
+ playersById: playerlist
+ };
+ case types.CHANGE_PAGE:
+ return {
+ ...state,
+ current: action.current
};
default: