├── .babelrc
├── .gitignore
├── README.md
├── config-overrides.js
├── images.d.ts
├── nodemon.json
├── package.json
├── public
├── favicon.ico
├── index.html
└── manifest.json
├── server
├── README.md
├── controller
│ ├── todo.js
│ └── user.js
├── db
│ └── connect.js
├── index.js
├── models
│ ├── todo.js
│ └── user.js
├── routers
│ ├── index.js
│ ├── todo.js
│ └── user.js
└── utils
│ ├── response.js
│ └── utils.js
├── src
├── App.module.less
├── App.test.tsx
├── App.tsx
├── actions
│ ├── todo.tsx
│ └── user.tsx
├── constance
│ ├── todo.tsx
│ ├── user.tsx
│ └── utils.tsx
├── index.css
├── index.tsx
├── logo.svg
├── reducer
│ ├── index.tsx
│ ├── todo.tsx
│ └── user.tsx
├── registerServiceWorker.ts
├── routes
│ ├── Login
│ │ ├── index.module.less
│ │ └── index.tsx
│ ├── Register
│ │ ├── index.module.less
│ │ └── index.tsx
│ └── Todo
│ │ ├── Todo.tsx
│ │ ├── TodoList.tsx
│ │ └── index.module.less
├── services
│ ├── todo.tsx
│ └── user.tsx
└── utils
│ ├── connect.tsx
│ ├── notification.tsx
│ ├── render.tsx
│ ├── request.tsx
│ └── utils.tsx
├── tsconfig.json
├── tsconfig.prod.json
├── tsconfig.test.json
├── tslint.json
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets":[
3 | [
4 | "env",
5 | {
6 | "target":{
7 | "node":"current"
8 | }
9 | }
10 | ],
11 | "stage-3"
12 | ]
13 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 |
6 | # testing
7 | /coverage
8 |
9 | # production
10 | /build
11 |
12 | # misc
13 | .DS_Store
14 | .env.local
15 | .env.development.local
16 | .env.test.local
17 | .env.production.local
18 |
19 | npm-debug.log*
20 | yarn-debug.log*
21 | yarn-error.log*
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 写英文md是我想学英语, 如果不习惯中式英文, [中文md点这里](https://github.com/binyellow/TS-TodoList-Koa/tree/develop-zh)
2 | # TS-TodoList-Koa
3 | todolist with TS Koa MongoDB Webpack
4 |
5 | ### plan
6 | - [x] Redux
7 | - [x] Router
8 | - [x] Koa
9 | - [x] learn mongoDB index
10 | - [x] encapsulated return params
11 | - [x] nginx to agency the local request
12 | - [x] add delete and toggle completed
13 | - [ ] add editRow,Cell. add Search
14 | - [x] register and login with token
15 | - [ ] docker,jenkins
16 |
17 | ### Some problem in the progress
18 | - [x] less-loader support css modules: react-app-rewire-less-with-modules
19 | - [x] tslint: import order problem
20 | ```js
21 | "rules": {
22 | "ordered-imports": false
23 | },
24 | ```
25 | - [x] antd problem about input>TextArea:
26 | - Open: node_modules/antd/lib/input/TextArea.d.ts
27 | - Insert import: import ResizeObserver from 'resize-observer-polyfill';
28 | - [link to antd](https://github.com/ant-design/ant-design/issues/13405)
29 | - [x] asy import the Component: [antd official document](https://ant.design/docs/react/use-in-typescript-cn)
30 | - [x] nodemon and babel-cli to translate ES6 to lower version and hot replace, use nodemon.json to config the ignore about hot watch file:
31 | ```js
32 | {
33 | "ignore": ["lib/*.js", "README"]
34 | }
35 | ```
36 | - [x] implicitly has an 'any' type: to install @types/moduleName to resolve this problem
37 | - [x] connect problem: [decorator and no](https://stackoverflow.com/questions/46861839/typescript-connect-react-redux-decorator-with-stateful-component)
38 | - [x] [params with shadow](https://stackoverflow.com/questions/52968903/shadowed-name-in-typescript-and-react-redux)
39 | - [x] Router props problem: import RouteComponentProps from react-router-dom to define the Component's props
40 | - [x] alias problem: Resolve alias issues by adding a path configuration in tsconfig
41 | - [x] static function problem: first, is should be put before constructor; second, specific the type of the method return values
--------------------------------------------------------------------------------
/config-overrides.js:
--------------------------------------------------------------------------------
1 | // const { injectBabelPlugin } = require('react-app-rewired');
2 | const path = require('path');
3 | const tsImportPluginFactory = require('ts-import-plugin');
4 | const { getLoader } = require("react-app-rewired");
5 | const rewireLessWithModule = require('react-app-rewire-less-with-modules');
6 |
7 | module.exports = function override (config, env) {
8 | // config = injectBabelPlugin(['import', {
9 | // libraryDirectory: 'es',
10 | // libraryName: 'antd',
11 | // style: true
12 | // }], config)
13 | const tsLoader = getLoader(
14 | config.module.rules,
15 | rule =>
16 | rule.loader &&
17 | typeof rule.loader === 'string' &&
18 | rule.loader.includes('ts-loader')
19 | );
20 |
21 | tsLoader.options = {
22 | getCustomTransformers: () => ({
23 | before: [ tsImportPluginFactory({
24 | libraryDirectory: 'es',
25 | libraryName: 'antd',
26 | style: 'css',
27 | }) ]
28 | })
29 | };
30 | config = rewireLessWithModule(config, env, {
31 | // modifyVars: {
32 | // '@primary-color': 'red',
33 | // '@link-color': '#1DA57A',
34 | // '@border-radius-base': '2px',
35 | // '@font-size-base': '16px',
36 | // '@line-height-base': '1.2'
37 | // },
38 | })
39 | // config.resolve = {
40 | // alias: {
41 | // // services: path.resolve(__dirname, 'src/services/'),
42 | // utils: path.resolve(__dirname, 'src/utils/')
43 | // },
44 | // extensions: [ '.ts', '.js', '.json', '.css', '.tsx', '.less' ],
45 | // };
46 | return config
47 | }
--------------------------------------------------------------------------------
/images.d.ts:
--------------------------------------------------------------------------------
1 | declare module '*.svg'
2 | declare module '*.png'
3 | declare module '*.jpg'
4 | declare module '*.jpeg'
5 | declare module '*.gif'
6 | declare module '*.bmp'
7 | declare module '*.tiff'
8 | declare module '*.less'
9 |
--------------------------------------------------------------------------------
/nodemon.json:
--------------------------------------------------------------------------------
1 | {
2 | "ignore": [
3 | "src/*.js",
4 | "src/**/*.js",
5 | "README",
6 | "src/*.tsx",
7 | "src/**/*.tsx",
8 | "node_modules",
9 | "public"
10 | ]
11 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "todolist",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "D": "^1.0.0",
7 | "react": "^16.6.3",
8 | "react-dom": "^16.6.3",
9 | "react-scripts-ts": "3.1.0"
10 | },
11 | "scripts": {
12 | "start": "react-app-rewired start --scripts-version react-scripts-ts",
13 | "build": "react-app-rewired build --scripts-version react-scripts-ts",
14 | "test": "react-app-rewired test --scripts-version react-scripts-ts --env=jsdom",
15 | "eject": "react-scripts eject",
16 | "koa": "nodemon --exec babel-node server/index.js"
17 | },
18 | "devDependencies": {
19 | "@types/jest": "^23.3.10",
20 | "@types/koa": "^2.0.47",
21 | "@types/node": "^10.12.12",
22 | "@types/react": "^16.7.13",
23 | "@types/react-dom": "^16.0.11",
24 | "@types/react-redux": "^6.0.11",
25 | "@types/react-router": "^4.4.2",
26 | "@types/react-router-dom": "^4.3.1",
27 | "@types/redux-logger": "^3.0.6",
28 | "@types/uuid": "^3.4.4",
29 | "antd": "3.10.9",
30 | "axios": "^0.18.0",
31 | "babel-cli": "^6.26.0",
32 | "babel-plugin-transform-decorators-legacy": "^1.3.5",
33 | "babel-preset-env": "^1.7.0",
34 | "babel-preset-stage-3": "^6.24.1",
35 | "jsonwebtoken": "^8.4.0",
36 | "koa": "^2.6.2",
37 | "koa-bodyparser": "^4.2.1",
38 | "koa-jwt": "^3.5.1",
39 | "koa-router": "^7.4.0",
40 | "koa2-cors": "^2.0.6",
41 | "less": "^3.9.0",
42 | "lodash-decorators": "^6.0.0",
43 | "mongoose": "^5.3.15",
44 | "querystringify": "^2.1.0",
45 | "react-app-rewire-less": "^2.1.3",
46 | "react-app-rewire-less-with-modules": "^0.0.4",
47 | "react-app-rewired": "^1.6.2",
48 | "react-redux": "^6.0.0",
49 | "react-router-dom": "^4.3.1",
50 | "redux": "^4.0.1",
51 | "redux-devtools-extension": "^2.13.7",
52 | "redux-logger": "^3.0.6",
53 | "redux-thunk": "^2.3.0",
54 | "ts-import-plugin": "^1.5.5",
55 | "typescript": "^3.2.2",
56 | "uuid": "^3.3.2"
57 | },
58 | "proxy": "http://127.0.0.1:3001"
59 | }
60 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/binyellow/TS-TodoList-Koa/eda75c3beb830b29889d99e287e81a094ea38e4f/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 |
--------------------------------------------------------------------------------
/server/README.md:
--------------------------------------------------------------------------------
1 | ### response body format
2 | ```js
3 | {
4 | failed: Boolean, // status of request
5 | message: String,
6 | content?: Array, // fetch list's content
7 | total?: Number, // total number of contents
8 | current?: Number, // current page number
9 | pageSize?: Number, // page size
10 | result?: String, // object of request
11 | }
12 | ```
--------------------------------------------------------------------------------
/server/controller/todo.js:
--------------------------------------------------------------------------------
1 | import todo from '../models/todo';
2 | import { successResponse, failedResponse } from '../utils/response';
3 | import { toSafeNumber } from '../utils/utils';
4 |
5 | async function add(ctx, next) {
6 | const { userId, content, completed } = ctx.request.body;
7 | const res = await todo.create({ userId, content, completed });
8 | if(res.length!==0) {
9 | ctx.body = {
10 | failed: false,
11 | message: res
12 | }
13 | } else {
14 | ctx.body = {
15 | failed: true,
16 | message: '新增失败'
17 | }
18 | }
19 | }
20 |
21 | async function fetchList(ctx, next) {
22 | const { userId, current = 0, pageSize = 10 } = toSafeNumber(ctx.query, ['current', 'pageSize']);
23 | const query = await todo.find({ userId });
24 | let res = await todo.find({ userId }).sort({time: -1}).skip((current)*pageSize).limit(pageSize);
25 | if(res instanceof Array && res.length>=0) {
26 | ctx.body = successResponse({
27 | current,
28 | pageSize,
29 | total: query.length,
30 | message: "查询成功",
31 | content: res,
32 | });
33 | } else {
34 | ctx.body = {
35 | failed: true,
36 | message: '无list',
37 | content: []
38 | }
39 | }
40 | }
41 |
42 | async function deleteItem(ctx, next) {
43 | const { _id } = ctx.request.body;
44 | const res = await todo.findOneAndDelete({ _id }).exec();
45 | if(res) {
46 | ctx.body = successResponse();
47 | } else {
48 | ctx.body = failedResponse();
49 | }
50 | }
51 |
52 | /**
53 | * 循环更新效率很低,Todo updateMany
54 | * @param {*} ctx
55 | * @param {function} next
56 | */
57 | async function toggleForeach(ctx, next) {
58 | const { toggleList = [] } = ctx.request.body;
59 | console.log('toggleList===>', toggleList);
60 | await new Promise((resolve, reject)=>{
61 | let res = undefined;
62 | toggleList.forEach(async (item, index)=> {
63 | const { _id, completed } = item;
64 | const result = todo.find({ _id });
65 | res = await result.update({ $set: { completed: !completed }}).exec();
66 | if(res) {
67 | if(index === toggleList.length - 1) {
68 | resolve(res);
69 | }
70 | } else {
71 | reject(res);
72 | }
73 | })
74 | }).then(res=> {
75 | ctx.body = successResponse();
76 | }, res=> {
77 | ctx.body = failedResponse();
78 | }).catch(err=> {
79 | ctx.body = failedResponse();
80 | })
81 | }
82 |
83 | async function toggle(ctx, next) {
84 | const { toggleList = [] } = ctx.request.body;
85 | console.log('toggleList===>', toggleList);
86 | const res = await todo.bulkWrite(toggleList.map(item=>{
87 | const { _id, completed } = item;
88 | return {
89 | updateOne: {
90 | filter: { _id },
91 | update: { completed: !completed },
92 | }
93 | }
94 | }));
95 | if(res) {
96 | ctx.body = successResponse();
97 | } else {
98 | ctx.body = failedResponse();
99 | }
100 | }
101 | module.exports = { add, fetchList, deleteItem, toggle }
--------------------------------------------------------------------------------
/server/controller/user.js:
--------------------------------------------------------------------------------
1 | import user from '../models/user';
2 | import { successResponse, failedResponse } from '../utils/response';
3 | import jsonwebtoken from 'jsonwebtoken';
4 |
5 | async function register(ctx, next) {
6 | const { name } = ctx.request.body;
7 | const res = await user.findOne({ name });
8 | if(res) {
9 | ctx.body = failedResponse({message: '用户名已存在', content: res});
10 | } else {
11 | const add = await user.create(ctx.request.body);
12 | ctx.body = successResponse({message: "注册成功", content: res});
13 | }
14 | }
15 |
16 | async function login(ctx) {
17 | const { name, passWord } = ctx.request.body;
18 | const res = await user.findOne({ name });
19 | if(res) {
20 | const passRes = await user.findOne({ name, passWord });
21 | if(passRes) {
22 | ctx.body = successResponse({
23 | message: "登录成功!",
24 | content: passRes,
25 | token: jsonwebtoken.sign({
26 | data: name,
27 | exp: Math.floor(Date.now() / 1000) + (60),
28 | }, 'huangbin')
29 | });
30 | } else {
31 | ctx.body = failedResponse({ message: "密码不正确!" });
32 | }
33 | } else {
34 | ctx.body = failedResponse({ message: '账号不存在!' });
35 | }
36 | }
37 |
38 | async function fetchUserList(ctx, next) {
39 | const content = await user.find();
40 | if(content) {
41 | ctx.body = {
42 | failed: false,
43 | message: '查询成功',
44 | content
45 | }
46 | } else {
47 | ctx.body = {
48 | failed: true,
49 | message: '无list',
50 | content: []
51 | }
52 | }
53 | }
54 | module.exports = { register, login, fetchUserList }
--------------------------------------------------------------------------------
/server/db/connect.js:
--------------------------------------------------------------------------------
1 | import mongoose from 'mongoose';
2 |
3 | const DB_URL = 'mongodb://localhost:27017/TodoList';
4 | export default () => {
5 | const connect = () => {
6 | // mongoose.Promise = require('bluebird');
7 | mongoose.connect(DB_URL, err => {
8 | if (err) {
9 | console.log(`===> Error connecting to mongoDB`);
10 | console.log(`Reason: ${err}`);
11 | } else {
12 | console.log(`===> Succeeded in connecting to mongoDB`);
13 | }
14 | });
15 | };
16 | connect();
17 |
18 | mongoose.connection.on('error', console.log);
19 | mongoose.connection.on('disconnected', connect);
20 |
21 | };
22 |
--------------------------------------------------------------------------------
/server/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | * server root file
3 | * @date: 2018-12-05
4 | * @author: HB
5 | * @version: 0.0.1
6 | * @copyright Copyright (c) 2018, Hand
7 | */
8 |
9 | import Koa from 'koa';
10 | import connect from './db/connect';
11 | import Todo from './models/todo';
12 | import bodyParser from 'koa-bodyparser';
13 | import cors from 'koa2-cors';
14 | import qs from 'querystring';
15 | import jwt from 'koa-jwt';
16 | import router from './routers/index';
17 | const app = new Koa();
18 |
19 | connect();
20 | app.use(jwt({
21 | secret: 'huangbin'
22 | }).unless({ path: [/^\/user/] }))
23 | .use(cors())
24 | .use(bodyParser())
25 | .use(router.routes()).use(router.allowedMethods());
26 | app.listen(3001, ()=>console.log('listen on 3001'));
--------------------------------------------------------------------------------
/server/models/todo.js:
--------------------------------------------------------------------------------
1 | import mongoose from 'mongoose';
2 | const Schema = mongoose.Schema;
3 |
4 | const TodoSchema = Schema({
5 | userId: { type: String, required: true },
6 | content: { type: String, required: true },
7 | completed: { type: Boolean, required: true, default: false },
8 | time: { type: Number, default: Date.now }
9 | })
10 |
11 | export default mongoose.model('Todo', TodoSchema);
--------------------------------------------------------------------------------
/server/models/user.js:
--------------------------------------------------------------------------------
1 | import mongoose from 'mongoose';
2 | const Schema = mongoose.Schema;
3 |
4 | const UserSchema = Schema({
5 | name: { type: String, required: true },
6 | passWord: { type: String, required: true },
7 | registerTime: { type: Number, default: Date.now }
8 | })
9 |
10 | export default mongoose.model('User', UserSchema);
--------------------------------------------------------------------------------
/server/routers/index.js:
--------------------------------------------------------------------------------
1 | import KoaRouter from 'koa-router';
2 | import todo from './todo';
3 | import user from './user';
4 |
5 | const router = new KoaRouter();
6 | router.use('/todo', todo.routes(), todo.allowedMethods());
7 | router.use('/user', user.routes(), user.allowedMethods());
8 |
9 | module.exports = router;
10 |
--------------------------------------------------------------------------------
/server/routers/todo.js:
--------------------------------------------------------------------------------
1 | import KoaRouter from 'koa-router';
2 | import { add, fetchList, deleteItem, toggle } from '../controller/todo';
3 | const todo = new KoaRouter();
4 |
5 | todo.post('/add', add);
6 | todo.get('/fetch', fetchList);
7 | todo.del('/delete', deleteItem);
8 | todo.put('/toggle', toggle);
9 |
10 | module.exports = todo;
--------------------------------------------------------------------------------
/server/routers/user.js:
--------------------------------------------------------------------------------
1 | import KoaRouter from 'koa-router';
2 | import { register, login, fetchUserList } from '../controller/user';
3 | const user = new KoaRouter();
4 |
5 | user.post('/register', register);
6 | user.post('/login', login);
7 | user.get('/fetchUserList', fetchUserList);
8 |
9 | module.exports = user;
--------------------------------------------------------------------------------
/server/utils/response.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 成功返回数据
3 | * @param {Object} {content, total, current, pageSize}
4 | * @param content 内容
5 | * @param total? 总条数
6 | * @param current? 当前页
7 | * @param pageSize? 分页大小
8 | * @returns Object
9 | */
10 | function successResponse({message = '操作成功', content, token, total = 10, current = 0, pageSize = 10} = {}) {
11 | const result = JSON.parse(JSON.stringify({content, total, current, pageSize, token}));
12 | return {
13 | message,
14 | failed: false,
15 | ...result
16 | }
17 | }
18 |
19 | /**
20 | * 失败返回数据
21 | * @param {Object} {content, message}
22 | * @param content? 内容
23 | * @param message 消息
24 | * @returns Object
25 | */
26 | function failedResponse({content = undefined, message = '操作失败'} = {}) {
27 | const result = JSON.parse(JSON.stringify({content, message}));
28 | return {
29 | failed: true,
30 | message,
31 | ...result
32 | }
33 | }
34 |
35 | module.exports = { successResponse, failedResponse }
36 |
--------------------------------------------------------------------------------
/server/utils/utils.js:
--------------------------------------------------------------------------------
1 | function toSafeNumber(params, arr) {
2 | let newPrams = { ...params };
3 | arr.forEach(item=>{
4 | if(params[item]){
5 | newPrams[item] = +params[item]
6 | }
7 | })
8 | return newPrams;
9 | }
10 |
11 | module.exports = { toSafeNumber }
--------------------------------------------------------------------------------
/src/App.module.less:
--------------------------------------------------------------------------------
1 | .app {
2 | display: flex;
3 | justify-content: center;
4 | color: red;
5 | }
--------------------------------------------------------------------------------
/src/App.test.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as 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/App.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import styles from './App.module.less';
3 |
4 | class App extends React.Component {
5 | public render() {
6 | return (
7 |
8 | 1
9 |
10 | );
11 | }
12 | }
13 |
14 | export default App;
15 |
--------------------------------------------------------------------------------
/src/actions/todo.tsx:
--------------------------------------------------------------------------------
1 | import { ADD, DELETE, COMPLETED } from '../constance/todo';
2 |
3 | export function updateState(payload: object) {
4 | return {
5 | payload,
6 | type: ADD,
7 | }
8 | }
9 | export function deleteTodo(payload: object) {
10 | return {
11 | payload,
12 | type: DELETE,
13 | }
14 | }
15 |
16 | export function toggleCompleted(payload: any) {
17 | return {
18 | payload,
19 | type: COMPLETED,
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/actions/user.tsx:
--------------------------------------------------------------------------------
1 | import { QUERY } from '../constance/user';
2 |
3 | export function updateState(payload: object) {
4 | return {
5 | payload,
6 | type: QUERY,
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/constance/todo.tsx:
--------------------------------------------------------------------------------
1 | const ADD = "ADD";
2 | const DELETE = "DELETE";
3 | const COMPLETED = "COMPLETED";
4 |
5 | export { ADD, DELETE, COMPLETED }
--------------------------------------------------------------------------------
/src/constance/user.tsx:
--------------------------------------------------------------------------------
1 | const QUERY = "QUERY";
2 |
3 | export { QUERY }
--------------------------------------------------------------------------------
/src/constance/utils.tsx:
--------------------------------------------------------------------------------
1 | export const PAGE_SIZE_OPTIONS = ['10', '20', '50', '100'];
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | @import '~antd/dist/antd.css';
2 |
3 | body {
4 | margin: 0;
5 | padding: 0;
6 | font-family: sans-serif;
7 | }
8 |
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 | import * as ReactDOM from 'react-dom';
3 | import { createStore, compose, applyMiddleware } from 'redux';
4 | import { Provider } from 'react-redux';
5 | import thunk from 'redux-thunk';
6 | import { composeWithDevTools } from 'redux-devtools-extension';
7 | import logger from 'redux-logger';
8 | import { LocaleProvider } from 'antd';
9 | import zh_CN from 'antd/lib/locale-provider/zh_CN';
10 | import { BrowserRouter, Route, Switch, Redirect } from 'react-router-dom';
11 |
12 | import reducer from './reducer';
13 | import Todo from './routes/Todo/Todo';
14 | import Login from './routes/Login';
15 | import Register from './routes/Register';
16 | import './index.css';
17 |
18 | const middleware = [logger, thunk];
19 | // const middleware = [thunk].concat(process.env.NODE_ENV === `development`?logger:[]);
20 | const store = createStore(reducer, compose(
21 | applyMiddleware(...middleware),
22 | composeWithDevTools()
23 | ))
24 | ReactDOM.render(
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 | ,
37 | document.getElementById('root') as HTMLElement
38 | );
39 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/reducer/index.tsx:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import todo from './todo';
3 | import user from './user';
4 |
5 | export default combineReducers({ todo, user });
--------------------------------------------------------------------------------
/src/reducer/todo.tsx:
--------------------------------------------------------------------------------
1 | import { ADD, DELETE, COMPLETED } from '../constance/todo';
2 |
3 | interface ActionProps {
4 | payload: any;
5 | type: string;
6 | }
7 | interface S {
8 | pagination: object,
9 | todoList: any[];
10 | }
11 | const initialState = {
12 | pagination: {},
13 | todoList: [],
14 | }
15 | export default function todo(state: S = initialState, action: ActionProps) {
16 | switch(action.type) {
17 | case ADD:
18 | return {
19 | ...state,
20 | ...action.payload
21 | }
22 | case DELETE:
23 | return {
24 | ...state,
25 | todoList: state.todoList.filter((item: any,index: number)=>action.payload.index!==index)
26 | }
27 | case COMPLETED:
28 | const { todoList } = state;
29 | const { selectedRowKeys } = action.payload;
30 | return {
31 | ...state,
32 | todoList: todoList.map((item: { _id: any, completed: boolean })=>{
33 | if(selectedRowKeys.includes(item._id)) {
34 | return {
35 | ...item,
36 | completed: true,
37 | }
38 | } else {
39 | if(item.completed) {
40 | return {
41 | ...item,
42 | completed: false,
43 | }
44 | }
45 | return item;
46 | }
47 | })
48 | }
49 | default:
50 | return state;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/src/reducer/user.tsx:
--------------------------------------------------------------------------------
1 | import { QUERY } from '../constance/user';
2 |
3 | interface ActionProps {
4 | payload: any;
5 | type: string;
6 | }
7 | interface S {
8 | userList: any[];
9 | }
10 | const initialState = {
11 | userList: [],
12 | }
13 | export default function user(state: S = initialState, action: ActionProps) {
14 | switch(action.type) {
15 | case QUERY:
16 | return {
17 | ...state,
18 | ...action.payload
19 | }
20 | default:
21 | return state;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/registerServiceWorker.ts:
--------------------------------------------------------------------------------
1 | // tslint:disable:no-console
2 | // In production, we register a service worker to serve assets from local cache.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on the 'N+1' visit to a page, since previously
7 | // cached resources are updated in the background.
8 |
9 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
10 | // This link also includes instructions on opting out of this behavior.
11 |
12 | const isLocalhost = Boolean(
13 | window.location.hostname === 'localhost' ||
14 | // [::1] is the IPv6 localhost address.
15 | window.location.hostname === '[::1]' ||
16 | // 127.0.0.1/8 is considered localhost for IPv4.
17 | window.location.hostname.match(
18 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
19 | )
20 | );
21 |
22 | export default function register() {
23 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
24 | // The URL constructor is available in all browsers that support SW.
25 | const publicUrl = new URL(
26 | process.env.PUBLIC_URL!,
27 | window.location.toString()
28 | );
29 | if (publicUrl.origin !== window.location.origin) {
30 | // Our service worker won't work if PUBLIC_URL is on a different origin
31 | // from what our page is served on. This might happen if a CDN is used to
32 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
33 | return;
34 | }
35 |
36 | window.addEventListener('load', () => {
37 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
38 |
39 | if (isLocalhost) {
40 | // This is running on localhost. Lets check if a service worker still exists or not.
41 | checkValidServiceWorker(swUrl);
42 |
43 | // Add some additional logging to localhost, pointing developers to the
44 | // service worker/PWA documentation.
45 | navigator.serviceWorker.ready.then(() => {
46 | console.log(
47 | 'This web app is being served cache-first by a service ' +
48 | 'worker. To learn more, visit https://goo.gl/SC7cgQ'
49 | );
50 | });
51 | } else {
52 | // Is not local host. Just register service worker
53 | registerValidSW(swUrl);
54 | }
55 | });
56 | }
57 | }
58 |
59 | function registerValidSW(swUrl: string) {
60 | navigator.serviceWorker
61 | .register(swUrl)
62 | .then(registration => {
63 | registration.onupdatefound = () => {
64 | const installingWorker = registration.installing;
65 | if (installingWorker) {
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the old content will have been purged and
70 | // the fresh content will have been added to the cache.
71 | // It's the perfect time to display a 'New content is
72 | // available; please refresh.' message in your web app.
73 | console.log('New content is available; please refresh.');
74 | } else {
75 | // At this point, everything has been precached.
76 | // It's the perfect time to display a
77 | // 'Content is cached for offline use.' message.
78 | console.log('Content is cached for offline use.');
79 | }
80 | }
81 | };
82 | }
83 | };
84 | })
85 | .catch(error => {
86 | console.error('Error during service worker registration:', error);
87 | });
88 | }
89 |
90 | function checkValidServiceWorker(swUrl: string) {
91 | // Check if the service worker can be found. If it can't reload the page.
92 | fetch(swUrl)
93 | .then(response => {
94 | // Ensure service worker exists, and that we really are getting a JS file.
95 | if (
96 | response.status === 404 ||
97 | response.headers.get('content-type')!.indexOf('javascript') === -1
98 | ) {
99 | // No service worker found. Probably a different app. Reload the page.
100 | navigator.serviceWorker.ready.then(registration => {
101 | registration.unregister().then(() => {
102 | window.location.reload();
103 | });
104 | });
105 | } else {
106 | // Service worker found. Proceed as normal.
107 | registerValidSW(swUrl);
108 | }
109 | })
110 | .catch(() => {
111 | console.log(
112 | 'No internet connection found. App is running in offline mode.'
113 | );
114 | });
115 | }
116 |
117 | export function unregister() {
118 | if ('serviceWorker' in navigator) {
119 | navigator.serviceWorker.ready.then(registration => {
120 | registration.unregister();
121 | });
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/routes/Login/index.module.less:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | display: flex;
3 | align-items: center;
4 | flex-direction: column;
5 | padding: 20px;
6 | :global {
7 | .ant-form {
8 | width: 300px;
9 | .ant-row {
10 | display: flex;
11 | margin: 10px 0 0 0;
12 | justify-content: center;
13 | }
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/src/routes/Login/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Form, Input, Button } from 'antd';
3 | import { withRouter, RouteComponentProps } from 'react-router-dom';
4 | import { Bind } from 'lodash-decorators';
5 | import { login } from '../../services/user';
6 | import notification from '../../utils/notification';
7 | import { getResponse } from '../../utils/utils';
8 |
9 | import styles from './index.module.less';
10 |
11 | interface LoginProps {
12 | form: any
13 | }
14 | const FormItem = Form.Item;
15 |
16 | class Login extends Component {
17 | @Bind()
18 | public handleLogin() {
19 | const { form: { validateFields } } = this.props;
20 | validateFields((err: object, values: object)=>{
21 | if(!err) {
22 | login(values).then(res=>{
23 | const result = getResponse(res);
24 | if(result) {
25 | const { _id } = result.content;
26 | notification.success();
27 | sessionStorage.setItem('token',result.token);
28 | const path = {
29 | pathname: `/todo/${_id}`,
30 | // state: {
31 | // name: 'huangbin',
32 | // age: 23
33 | // },
34 | // search: 'name=huang&age=23'
35 | }
36 | this.props.history.push(path);
37 | }
38 | })
39 | }
40 | })
41 | }
42 | public render() {
43 | const { form: { getFieldDecorator } } = this.props;
44 | return (
45 |
46 |
69 |
70 | );
71 | }
72 | }
73 |
74 | export default withRouter(Form.create()(Login));
--------------------------------------------------------------------------------
/src/routes/Register/index.module.less:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | display: flex;
3 | align-items: center;
4 | flex-direction: column;
5 | padding: 20px;
6 | :global {
7 | .ant-form {
8 | width: 300px;
9 | .ant-row {
10 | display: flex;
11 | margin: 10px 0 0 0;
12 | justify-content: center;
13 | }
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/src/routes/Register/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Form, Input, Button } from 'antd';
3 | import { Link, withRouter, RouteComponentProps } from 'react-router-dom';
4 |
5 | import notification from '../../utils/notification';
6 | import { getResponse } from '../../utils/utils';
7 | import { register } from '../../services/user';
8 | import { Bind } from 'lodash-decorators';
9 | import styles from './index.module.less';
10 |
11 | interface RegisterProps {
12 | form: any
13 | }
14 | const FormItem = Form.Item;
15 | class Register extends Component {
16 | @Bind()
17 | public handleRegister() {
18 | const { form: { validateFields } } = this.props;
19 | validateFields((err: any,values: any)=>{
20 | if(!err) {
21 | register(values).then(res=>{
22 | const result = getResponse(res);
23 | if(result) {
24 | notification.success();
25 | }
26 | })
27 | }
28 | })
29 | }
30 | public render() {
31 | const { form: { getFieldDecorator } } = this.props;
32 | return (
33 |
34 |
58 |
59 | );
60 | }
61 | }
62 |
63 | export default withRouter(Form.create()(Register));
--------------------------------------------------------------------------------
/src/routes/Todo/Todo.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Button, Input, Form } from 'antd';
3 | import uuid from 'uuid/v4';
4 | import { connect } from 'react-redux';
5 | import { Bind } from 'lodash-decorators';
6 | import { withRouter, RouteComponentProps } from 'react-router-dom';
7 |
8 | import { fetchUserList } from 'service/user';
9 | import { getResponse } from 'utils/utils';
10 | import notification from 'utils/notification';
11 | import * as actions from '../../actions/todo'; // shadow name problem must to import *
12 | import * as userActions from '../../actions/user';
13 | import { add } from '../../services/todo';
14 | import TodoList from './TodoList';
15 | import styles from './index.module.less';
16 |
17 | interface TodoProps {
18 | updateState: any,
19 | updateUser: any,
20 | form: any,
21 | todoState: any,
22 | userId: number,
23 | }
24 | interface S {
25 | userId: number;
26 | listDom: any;
27 | }
28 | const FormItem = Form.Item;
29 | // @connect(
30 | // (state: any)=>state,
31 | // { updateState }
32 | // )
33 | class Todo extends Component {
34 | constructor(props: TodoProps & RouteComponentProps) {
35 | super(props);
36 | const { userId } = this.props.match.params as TodoProps;
37 | this.state = {
38 | userId,
39 | listDom: undefined,
40 | }
41 | }
42 | public componentDidMount() {
43 | const { updateUser } = this.props;
44 | fetchUserList({}).then(res=>{
45 | const result = getResponse(res);
46 | if(result) {
47 | updateUser({ userList: result.content });
48 | }
49 | })
50 | }
51 | @Bind()
52 | public handleAdd() {
53 | const {
54 | updateState,
55 | form: { validateFieldsAndScroll, resetFields },
56 | todoState: { todoList = [] }
57 | } = this.props;
58 | const { userId, listDom } = this.state;
59 | validateFieldsAndScroll((err: any, values: any)=>{
60 | if(!err) {
61 | const { content } = values;
62 | content && updateState({
63 | todoList: [
64 | { userId, content, completed: false, time: Date.now(), _id: uuid() },
65 | ...todoList
66 | ]
67 | });
68 | add({ userId, content, completed: false }).then(res=>{
69 | if(res) {
70 | notification.success();
71 | listDom && listDom.handleSearchList({ userId });
72 | }
73 | })
74 | resetFields();
75 | }
76 | })
77 | }
78 |
79 | @Bind()
80 | public handleRef(node: any) {
81 | this.setState({
82 | listDom: node
83 | })
84 | }
85 | public render() {
86 | const { userId } = this.state;
87 | const { form } = this.props;
88 | const { getFieldDecorator } = form;
89 | return (
90 |
91 |
105 |
106 |
107 | );
108 | }
109 | }
110 |
111 | export default withRouter(connect(
112 | (state: any)=>({todoState: state.todo}),
113 | { updateState: actions.updateState, updateUser: userActions.updateState }
114 | )(Form.create()(Todo)));
--------------------------------------------------------------------------------
/src/routes/Todo/TodoList.tsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { Table, Button, Badge } from 'antd';
3 | import { connect } from 'react-redux';
4 | import { Bind } from 'lodash-decorators';
5 | import { getResponse, createPagination } from 'utils/utils';
6 | import { renderTimeStamp } from 'utils/render';
7 | import { fetchTodoList, deleteTodo, toggleTodo } from 'service/todo';
8 | import * as actions from '../../actions/todo';
9 | import notification from 'utils/notification';
10 |
11 | interface TodoListProps {
12 | user: any,
13 | todoState: any,
14 | updateState: any,
15 | toggleCompleted: any,
16 | userId: number,
17 | onRef: any,
18 | }
19 | interface S {
20 | loading: boolean;
21 | selectedRowKeys: string[];
22 | }
23 | interface RecordProps {
24 | _id: any;
25 | completed: boolean,
26 | }
27 | class TodoList extends Component {
28 | // public static getDerivedStateFromProps(props: TodoListProps, state: S): any {
29 | // const { loading } = state;
30 | // const { loading: nextLoading } = props;
31 | // if( !loading && nextLoading !== loading ) {
32 | // return {
33 | // loading: nextLoading,
34 | // }
35 | // }
36 | // return null;
37 | // }
38 | constructor(props: TodoListProps) {
39 | super(props);
40 | props.onRef(this);
41 | this.state = {
42 | loading: true,
43 | selectedRowKeys: [],
44 | }
45 | }
46 | public componentDidMount() {
47 | const { userId } = this.props;
48 | this.handleSearchList({ userId });
49 | }
50 | @Bind()
51 | public handleSearchList(params: object) {
52 | this.setState({ loading: true });
53 | const { updateState } = this.props;
54 | fetchTodoList(params).then(res=>{
55 | const result = getResponse(res);
56 | if(result) {
57 | const { content } = result;
58 | this.setState({ loading: false });
59 | updateState({ todoList: content, pagination: createPagination(result) });
60 | this.handleSetSelectedRowKeys(content);
61 | }
62 | })
63 | }
64 | @Bind()
65 | public handleSetSelectedRowKeys(content: any[]) {
66 | const selectedRowKeys: any[] = [];
67 | content.forEach((item: { _id: any, completed: boolean })=>{
68 | if(item.completed) {
69 | selectedRowKeys.push(item._id);
70 | }
71 | })
72 | console.log(selectedRowKeys);
73 | this.setState({ selectedRowKeys });
74 | }
75 | @Bind()
76 | public handleDelete(record: RecordProps) {
77 | deleteTodo({_id: record._id}).then(res=>{
78 | const result = getResponse(res);
79 | if(result) {
80 | notification.success();
81 | const { userId } = this.props;
82 | this.handleSearchList({ userId });
83 | }
84 | })
85 | }
86 | @Bind()
87 | public handleChangeRow(selectedRowKeys: string[], selectedRows: any[]) {
88 | const { userId, todoState: { todoList = [] } } = this.props;
89 | // this.setState({ selectedRowKeys });
90 | // toggleCompleted({ selectedRowKeys });
91 | const toggleList: object[] = [];
92 | todoList.forEach((item: RecordProps)=> {
93 | if(selectedRowKeys.indexOf(item._id) >= 0) {
94 | if(!item.completed) {
95 | toggleList.push(item); // 如果被选中但是先前没完成
96 | }
97 | } else if(item.completed) {
98 | toggleList.push(item); // 或者之前没被选中 现在是完成
99 | }
100 | })
101 | toggleTodo({ toggleList }).then((res: object)=> {
102 | const result = getResponse(res);
103 | if(result) {
104 | notification.success();
105 | this.handleSearchList({ userId });
106 | }
107 | })
108 | }
109 | @Bind()
110 | public handleChangePage(pagination: { current: number, pageSize: number }) {
111 | const { updateState, userId } = this.props;
112 | const { current, pageSize } = pagination;
113 | this.setState({ loading: true });
114 | fetchTodoList({ userId, current, pageSize }).then(res=>{
115 | const result = getResponse(res);
116 | if(result) {
117 | updateState({ todoList: result.content, pagination: createPagination(result) });
118 | this.setState({ loading: false });
119 | }
120 | })
121 | }
122 | public render() {
123 | const { loading = false, selectedRowKeys } = this.state;
124 | const { todoState: { todoList = [], pagination = {} }, user, userId } = this.props;
125 | const rowSelection = {
126 | selectedRowKeys,
127 | onChange: this.handleChangeRow,
128 | }
129 | const { name } = user.userList.length > 0 && user.userList.find((item: { _id: any })=> item._id === userId);
130 | const columns = [
131 | {
132 | title: '用户名',
133 | dataIndex: 'userId',
134 | width: 250,
135 | render: ()=> {name},
136 | },
137 | {
138 | title: '内容',
139 | dataIndex: 'content',
140 | width: 250,
141 | render: (val: string, record: RecordProps) =>
142 | record.completed ?
143 | {val} :
144 | val,
145 | },
146 | {
147 | title: '是否已完成',
148 | dataIndex: 'completed',
149 | width: 120,
150 | align: 'center' as 'center',
151 | render: (value: boolean) => value ? : ,
152 | },
153 | {
154 | title: '备忘时间',
155 | dataIndex: 'time',
156 | width: 250,
157 | render: renderTimeStamp,
158 | },
159 | {
160 | title: '操作',
161 | dataIndex: 'operation',
162 | align: 'center' as 'center',
163 | width: 100,
164 | render: (val: string, record: RecordProps, index: number) =>
165 | ,
166 | }
167 | ];
168 | const tableProps = {
169 | columns,
170 | loading,
171 | pagination,
172 | rowSelection,
173 | rowKey: '_id',
174 | scroll: { y: 400 },
175 | bordered: true,
176 | dataSource: todoList,
177 | onChange: this.handleChangePage,
178 | }
179 | return (
180 |
181 | )
182 | }
183 | }
184 | export default connect(
185 | (state: any)=>({todoState: state.todo, user: state.user}),
186 | { updateState: actions.updateState, toggleCompleted: actions.toggleCompleted }
187 | )(TodoList);
188 |
--------------------------------------------------------------------------------
/src/routes/Todo/index.module.less:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | display: flex;
3 | align-items: center;
4 | flex-flow: column nowrap;
5 | :global {
6 | p {
7 | margin: 0;
8 | }
9 | }
10 | .todo {
11 | display: flex;
12 | align-items: center;
13 | justify-content: center;
14 | padding: 20px;
15 | color: rgba(24, 144, 255, .8);
16 | :global {
17 | .ant-row,.ant-form-item {
18 | display: flex;
19 | flex-direction: row;
20 | margin-bottom: 0;
21 | }
22 | .ant-input {
23 | width: 150px;
24 | margin: 0 20px;
25 | }
26 | }
27 | }
28 | }
--------------------------------------------------------------------------------
/src/services/todo.tsx:
--------------------------------------------------------------------------------
1 | import request from '../utils/request';
2 | import { parseParams } from 'utils/utils';
3 |
4 | export async function add(params: any) {
5 | return request(`/todo/add`, {
6 | method: 'POST',
7 | data: params,
8 | })
9 | }
10 |
11 | export async function fetchTodoList(params: any) {
12 | return request(`/todo/fetch`, {
13 | method: 'GET',
14 | params: parseParams(params),
15 | })
16 | }
17 |
18 | export async function deleteTodo(params: any) {
19 | return request(`/todo/delete`, {
20 | method: 'DELETE',
21 | data: params,
22 | })
23 | }
24 |
25 | export async function toggleTodo(params: any) {
26 | return request(`/todo/toggle`, {
27 | method: 'PUT',
28 | data: params,
29 | })
30 | }
--------------------------------------------------------------------------------
/src/services/user.tsx:
--------------------------------------------------------------------------------
1 | import request from '../utils/request';
2 |
3 | export async function register(params: any) {
4 | return request(`/user/register`, {
5 | method: 'POST',
6 | data: params,
7 | })
8 | }
9 |
10 | export async function login(params: any) {
11 | return request(`/user/login`, {
12 | method: 'POST',
13 | data: params,
14 | })
15 | }
16 |
17 | export async function fetchUserList(params: any) {
18 | return request(`/user/fetchUserList`, {
19 | method: 'GET',
20 | params,
21 | })
22 | }
23 |
--------------------------------------------------------------------------------
/src/utils/connect.tsx:
--------------------------------------------------------------------------------
1 | import { connect as connectComponent } from 'react-redux';
2 |
3 | export const connect = (mapStateToProps: any, actions: any) => {
4 | return (target: any) => (
5 | connectComponent(mapStateToProps, actions)(target) as any
6 | );
7 | };
--------------------------------------------------------------------------------
/src/utils/notification.tsx:
--------------------------------------------------------------------------------
1 | import { notification } from 'antd';
2 |
3 | interface ParamsProps {
4 | message: string
5 | description: string
6 | // [key: string]: any
7 | }
8 | /**
9 | * 操作成功通知提示
10 | * @function success
11 | * @param {object} params - 默认属性
12 | * @param {?string} [params.message=操作成功] - 提示信息
13 | * @param {?string} [params.description] - 详细描述
14 | */
15 | function success(params?: ParamsProps) {
16 | const { message = "操作成功", description = "", ...others } = params || {};
17 | notification.success({
18 | message,
19 | description,
20 | duration: 2,
21 | ...others,
22 | });
23 | }
24 |
25 | // /**
26 | // * 操作失败通知提示
27 | // * @function error
28 | // * @param {object} params - 默认属性
29 | // * @param {?string} [params.message=操作失败] - 提示信息
30 | // * @param {?string} [params.description] - 详细描述
31 | // */
32 | // function error({ message:string = "操作失败", description: string = "", ...others: any }) {
33 | // notification.error({
34 | // message: message,
35 | // description,
36 | // duration: 2,
37 | // ...others,
38 | // });
39 | // }
40 |
41 | // /**
42 | // * 操作异常通知提示
43 | // * @function warning
44 | // * @param {object} params - 默认属性
45 | // * @param {?string} [params.message=操作异常] - 提示信息
46 | // * @param {?string} [params.description] - 详细描述
47 | // */
48 | // function warning({ message: string, description: string, ...others: any }) {
49 | // notification.warning({
50 | // message: message || "操作异常",
51 | // description,
52 | // duration: 2,
53 | // ...others,
54 | // });
55 | // }
56 |
57 | // /**
58 | // * 操作信息通知提示
59 | // * @function info
60 | // * @param {object} config - 默认属性
61 | // * @param {?string} [params.message] - 提示信息
62 | // * @param {?string} [params.description] - 详细描述
63 | // */
64 | // function info({ message: string, description: string, ...others: any }) {
65 | // notification.info({
66 | // message,
67 | // description,
68 | // duration: 2,
69 | // ...others,
70 | // });
71 | // }
72 |
73 | export default {
74 | success,
75 | // error,
76 | // warning,
77 | // info,
78 | };
79 |
--------------------------------------------------------------------------------
/src/utils/render.tsx:
--------------------------------------------------------------------------------
1 | function renderTimeStamp(time: number) {
2 | return new Date(time).toLocaleString();
3 | }
4 |
5 | export { renderTimeStamp }
--------------------------------------------------------------------------------
/src/utils/request.tsx:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import { notification } from 'antd';
3 |
4 | notification.config({
5 | placement: 'bottomRight',
6 | });
7 |
8 | function checkStatus(response: any) {
9 | if (response.status >= 200 && response.status < 300) {
10 | return response;
11 | }
12 |
13 | const errorText = response.statusText;
14 | const error = new Error(errorText);
15 | // error.name = response.status;
16 | // error.response = response;
17 | throw error;
18 | }
19 |
20 | export default function request(url: string, options: any) {
21 | const newOptions = { responseType: 'json', ...options };
22 | // newOptions.body = JSON.stringify(newOptions.body);
23 | const token = sessionStorage.getItem('token');
24 | return axios({
25 | url,
26 | headers: {
27 | 'Authorization': `bearer ${token}`
28 | },
29 | ...newOptions
30 | })
31 | .then(checkStatus)
32 | .then(response => {
33 | if (response.status === 204) {
34 | return {};
35 | }
36 | if (newOptions.responseType === 'blob') {
37 | return response.blob();
38 | }
39 | return response;
40 | })
41 | .catch(e => {
42 | const status = e.name;
43 | if (status === 401) {
44 | // removeAccessToken();
45 | window.location.href = "localhost:3000";
46 | return;
47 | }
48 |
49 | notification.error({
50 | message: `请求错误 ${status}`,
51 | description: e.message,
52 | });
53 | });
54 | }
--------------------------------------------------------------------------------
/src/utils/utils.tsx:
--------------------------------------------------------------------------------
1 | import { notification } from 'antd';
2 | import { PAGE_SIZE_OPTIONS } from 'constance/utils';
3 |
4 | interface ResponseProps {
5 | current: number;
6 | pageSize: number;
7 | total: number;
8 | }
9 | function getResponse(response: any) {
10 | if (response) {
11 | if(response.data.failed) {
12 | notification.error({
13 | message: '请求错误',
14 | description: response.data.message,
15 | });
16 | } else {
17 | return response.data;
18 | }
19 | }
20 | }
21 |
22 | function createPagination(response: ResponseProps) {
23 | if(response) {
24 | return {
25 | showSizeChanger: true,
26 | pageSizeOptions: [...PAGE_SIZE_OPTIONS],
27 | current: response.current + 1,
28 | pageSize: response.pageSize,
29 | total: response.total,
30 | showTotal: (total: number, range: any) => `显示 ${range[0]} - ${range[1]} 共 ${total} 条`
31 | }
32 | }
33 | return {}
34 | }
35 |
36 | function parseParams(params: ResponseProps) {
37 | const { current = 1, pageSize = 10, ...other } = params;
38 | return {
39 | current: current - 1,
40 | pageSize,
41 | ...other,
42 | }
43 | }
44 |
45 | export { getResponse, createPagination, parseParams }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": ".",
4 | "outDir": "build/dist",
5 | "module": "esnext",
6 | "target": "es5",
7 | "lib": ["es6", "dom"],
8 | "sourceMap": true,
9 | "allowJs": true,
10 | "jsx": "react",
11 | "moduleResolution": "node",
12 | // "rootDir": "src",
13 | "forceConsistentCasingInFileNames": true,
14 | "noImplicitReturns": true,
15 | "noImplicitThis": true,
16 | "noImplicitAny": true,
17 | "importHelpers": true,
18 | "strictNullChecks": true,
19 | "suppressImplicitAnyIndexErrors": true,
20 | "noUnusedLocals": true,
21 | "experimentalDecorators": true,
22 | "allowSyntheticDefaultImports": true,
23 | "paths": {
24 | "utils/*": ["./src/utils/*"],
25 | "service/*": ["./src/services/*"],
26 | "constance/*": ["./src/constance/*"],
27 | "actions/*": ["./src/actions/*"],
28 | }
29 | },
30 | "exclude": [
31 | "node_modules",
32 | "build",
33 | "scripts",
34 | "acceptance-tests",
35 | "webpack",
36 | "jest",
37 | "src/setupTests.ts"
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------
/tsconfig.prod.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json"
3 | }
--------------------------------------------------------------------------------
/tsconfig.test.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "./tsconfig.json",
3 | "compilerOptions": {
4 | "module": "commonjs"
5 | }
6 | }
--------------------------------------------------------------------------------
/tslint.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"],
3 | "rules": {
4 | "ordered-imports": false,
5 | "interface-name" : [true, "never-prefix"],
6 | "no-console": [true, "warning"],
7 | "no-unused-expression": false,
8 | "jsx-boolean-value": false,
9 | "jsx-no-lambda": false,
10 | "object-literal-sort-keys": false
11 | },
12 | "linterOptions": {
13 | "exclude": [
14 | "config/**/*.js",
15 | "node_modules/**/*.ts",
16 | "coverage/lcov-report/*.js",
17 | "server/**/*.js"
18 | ]
19 | }
20 | }
21 |
--------------------------------------------------------------------------------