add: 添加登录功能

This commit is contained in:
SunnyQjm committed 2019-10-30 18:06:45 +08:00
1 parent ab71d74755
commit b220b2e3ca
13 files changed
+400 -10

No files matched your search

+8 -1
View File
@@ -33,7 +33,14 @@ const config: IConfig = {
}],
],
sass: {},
disableCSSModules: true
disableCSSModules: true,
proxy: {
"/api": {
"target": "http://localhost:3000/",
"changeOrigin": true,
"pathRewrite": { "^/api" : "" }
}
}
};
export default config;
+2
View File
@@ -9,9 +9,11 @@
"precommit": "lint-staged"
},
"dependencies": {
"@types/axios": "^0.14.0",
"@types/react-addons-shallow-compare": "^0.14.22",
"@types/react-transition-group": "^4.2.3",
"antd": "^3.19.5",
"axios": "^0.19.0",
"dva": "^2.6.0-beta.6",
"for-editor": "^0.3.4",
"highlight.js": "^9.15.10",
+16
View File
@@ -0,0 +1,16 @@
export interface Action<T> {
type: string,
data: T
}
export interface Operators {
call: any,
put: any,
select: any,
}
export interface DispatchProps {
dispatch: any
}
+6
View File
@@ -0,0 +1,6 @@
export interface BaseResponseBody<T> {
statusCode: number;
error: string;
message: string;
data: T;
}
+90
View File
@@ -0,0 +1,90 @@
import axios from 'axios';
import {BaseResponseBody} from "@/data/interface/response.interface";
import {StatusCode} from "@/data/network/status-code";
import {User} from "@/data/user";
export const API = {
INFO: {
login: '/login',
register: '/register'
},
global: {
token: ''
},
easyRequest<T>(type: string, url: string, data: any = {}, config: any = {}): Promise<T> {
// 统一添加相应的头部
if (config.headers) {
config.headers = {
'Content-Type': 'application/json',
// 'authorization': `Bearer ${API.global.token}`,
...config.headers,
}
} else {
config.headers = {
'Content-Type': 'application/json',
// 'authorization': `Bearer ${API.global.token}`
}
}
let requestPromise;
switch (type.trim().toLowerCase()) {
case 'get':
requestPromise = axios.get(url, {
params: data,
...config,
});
break;
case 'post':
requestPromise = axios.post(url, data, config);
break;
case 'put':
break;
default:
break;
}
if (requestPromise) {
return requestPromise
.then((result: any) => {
console.log(result);
return result.data;
})
.then((responseData: BaseResponseBody<T>) => {
console.log(responseData);
// 同意处理状态码
switch (responseData.statusCode) {
case StatusCode.SUCCESS:
break;
case StatusCode.NEED_ADMIN_PERMISSION:
break;
case StatusCode.COMMENT_ERROR:
break;
case StatusCode.NEED_LOGIN:
break;
}
if(responseData.statusCode === StatusCode.SUCCESS) {
return responseData.data;
} else {
return Promise.reject(responseData);
}
});
}
return Promise.reject('请求类型不对');
},
easyGet<T>(url: string, data: any, config: any = {}): Promise<T> {
return API.easyRequest<T>('get', url, data, config);
},
eastPost<T>(url: string, data: any, config: any = {}): Promise<T> {
return API.easyRequest<T>('post', url, data, config);
},
////////////////////////////////////////////////////////////
///////// 用户相关接口
////////////////////////////////////////////////////////////
login: (username: string, password: string): Promise<User> => {
return API.eastPost<User>('/api/login', {
username,
password
})
}
};
+6
View File
@@ -0,0 +1,6 @@
export const StatusCode = {
SUCCESS: 0, // 成功
COMMENT_ERROR: -1, // 通用错误
NEED_LOGIN: -2, // 需要登录
NEED_ADMIN_PERMISSION: -3, // 需要管理员权限
};
+8
View File
@@ -0,0 +1,8 @@
export class User {
id: number = -1;
username: string = '';
password: string = '';
avatar: string = '';
nickName: string = '';
age: number = 0;
}
+16 -3
View File
@@ -5,6 +5,8 @@ import {
NavBarComponent
} from '../components';
import RouterTypes from "umi/routerTypes";
import {LocalStorageKeys, LocalStorageManager} from "@/manager/storage.manager";
import { connect } from 'dva';
const menu = [
{
@@ -26,7 +28,7 @@ const menu = [
interface BasicLayoutProps extends RouterTypes {
dispatch: any
}
interface BasicLayoutState {
@@ -38,6 +40,13 @@ class BasicLayout extends React.Component<BasicLayoutProps, BasicLayoutState> {
super(props);
}
componentDidMount(): void {
console.log('global layout mount');
this.props.dispatch({
type: 'admin/init'
});
}
componentDidUpdate(prevProps: any) {
if (this.props.location !== prevProps.location) {
window.scrollTo(0, 0);
@@ -45,7 +54,7 @@ class BasicLayout extends React.Component<BasicLayoutProps, BasicLayoutState> {
}
render(): React.ReactElement<any, string | React.JSXElementConstructor<any>> | string | number | {} | React.ReactNodeArray | React.ReactPortal | boolean | null | undefined {
if (this.props.location.pathname.startsWith('/admin')) {
if (this.props.location.pathname.startsWith('/admin') || this.props.location.pathname.startsWith('/login')) {
return (
<div>
{this.props.children}
@@ -71,4 +80,8 @@ class BasicLayout extends React.Component<BasicLayoutProps, BasicLayoutState> {
}
}
export default withRouter(BasicLayout);
function mapStateToProps(state: any) {
return state;
}
export default connect(mapStateToProps)(withRouter(BasicLayout));
+22
View File
@@ -0,0 +1,22 @@
export const LocalStorageKeys = {
USER: 'user'
};
export const LocalStorageManager = {
save<T>(key: string, value: T) {
let insertValue = JSON.stringify(value);
localStorage.setItem(key, insertValue);
},
getString(key: string) {
return localStorage.getItem(key);
},
getObj(key: string) {
const result = localStorage.getItem(key);
if(result) {
return JSON.parse(result);
}
return null;
}
};
+52
View File
@@ -0,0 +1,52 @@
import {Action, Operators} from "@/data/interface/dva.interface";
import {API} from "@/data/network/api";
import {User} from "@/data/user";
import router from 'umi/router';
import {} from 'antd'
import {LocalStorageKeys, LocalStorageManager} from "@/manager/storage.manager";
export interface AdminModelState {
user: User,
loginIng: boolean
}
export default {
namespace: 'admin',
state: {
user: null,
},
reducers: {
updateUser(state: any, action: Action<User>) {
console.log('fuck?', action);
return {
...state,
user: action.data
}
}
},
effects: {
* init(action: Action<any>, operators: Operators) {
const user = yield LocalStorageManager.getObj(LocalStorageKeys.USER);
if(user) {
yield operators.put({
type: 'updateUser',
data: user
});
} else { // 没有登录则跳转到登录页
router.replace('/login');
}
yield console.log('init');
},
* login(action: Action<any>, operators: Operators) {
const result: User = yield operators.call(API.login, action.data.username, action.data.password);
yield operators.put({
type: 'updateUser',
data: result
});
LocalStorageManager.save(LocalStorageKeys.USER, result);
router.replace('/admin');
}
}
}
+17 -6
View File
@@ -11,11 +11,13 @@ import RouterTypes from "umi/routerTypes";
import withRouter from 'umi/withRouter';
import {SelectParam} from "antd/es/menu";
import router from 'umi/router';
import { connect } from 'dva';
import {User} from "@/data/user";
const {Header, Content, Footer, Sider} = Layout;
interface AdminLayoutProps extends RouterTypes {
user: User,
}
interface AdminLayoutState {
@@ -50,9 +52,14 @@ class AdminLayout extends React.Component<AdminLayoutProps, AdminLayoutState> {
const {
collapsed
} = this.state;
const {
user
} = this.props;
const menu = (
<Menu>
<Menu.Item>退出登录</Menu.Item>
<Menu.Item onClick={() => {
router.replace('/login')
}}>退出登录</Menu.Item>
</Menu>
);
@@ -89,7 +96,6 @@ class AdminLayout extends React.Component<AdminLayoutProps, AdminLayoutState> {
<Layout style={{marginLeft: collapsed ? 80 : 200}}>
<Header
className='admin-layout__header'
// style={{width: collapsed ? 'calc(100% - 80px)' : 'calc(100% - 200px)'}}
>
<Dropdown overlay={menu}>
<a>
@@ -97,9 +103,8 @@ class AdminLayout extends React.Component<AdminLayoutProps, AdminLayoutState> {
className='admin-layout__header_avatar'
src='https://avatars2.githubusercontent.com/u/29176413?s=400&u=f78127887499acf06545f9cbe298ce388201dfed&v=4'
/>
<span className='admin-layout__header_username'>SunnyQjm</span>
<span className='admin-layout__header_username'>{user && user.username}</span>
</a>
</Dropdown>
</Header>
<Content className='admin-layout__content'>
@@ -112,4 +117,10 @@ class AdminLayout extends React.Component<AdminLayoutProps, AdminLayoutState> {
}
}
export default withRouter(AdminLayout);
function mapStateToProps(state: any) {
return {
user: state.admin.user
};
}
export default connect(mapStateToProps)(withRouter(AdminLayout));
+55
View File
@@ -0,0 +1,55 @@
.login-page {
display: flex;
align-items: center;
//justify-content: center;
height: 100vh;
width: 100%;
flex-direction: column;
&__title {
position: relative;
color: #222;
margin-top: 100px;
&::before {
content: '';
background: #222;
height: 2px;
width: 80%;
position: absolute;
top: -3px;
right: 10%;
animation: width-show 1s;
}
&::after {
content: '';
background: #222;
height: 2px;
width: 80%;
position: absolute;
bottom: -5px;
left: 10%;
animation: width-show 1s;
}
font-size: 30px;
font-weight: bold;
text-align: center;
animation: fadeInDown 1s;
}
&__description {
font-size: 14px;
margin: 20px 0 40px;
color: rgba(0, 0, 0, .45);
}
}
.login-form {
max-width: 300px;
}
.login-form-forgot {
float: right;
}
.login-form-button {
width: 100%;
}
+102
View File
@@ -0,0 +1,102 @@
import React, {FormEvent} from "react";
import './index.scss'
import {Form, Icon, Input, Button, Checkbox} from 'antd';
import { connect } from 'dva';
import {DispatchProps} from "@/data/interface/dva.interface";
interface LoginPageProps extends DispatchProps{
form: any,
loading: boolean
}
interface LoginPageState {
}
/**
* 登录页面
*/
class LoginPage extends React.Component<LoginPageProps, LoginPageState> {
constructor(props: LoginPageProps) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(e: FormEvent) {
e.preventDefault();
this.props.form.validateFields((err: any, values: object) => {
if (!err) {
console.log('Received values of form: ', values);
this.props.dispatch({
type: 'admin/login',
data: values
})
}
});
};
render(): React.ReactElement<any, string | React.JSXElementConstructor<any>> | string | number | {} | React.ReactNodeArray | React.ReactPortal | boolean | null | undefined {
const {getFieldDecorator} = this.props.form;
const {
loading
} = this.props;
return (
<div className='login-page'>
<span className='login-page__title'>
{'建明 | Ming.J'}
</span>
<span className='login-page__description'>每一个不曾起舞的日子都是对生命的辜负!</span>
<Form onSubmit={this.handleSubmit} className="login-form">
<Form.Item>
{getFieldDecorator('username', {
rules: [{required: true, message: '请输入用户名!'}],
})(
<Input
prefix={<Icon type="user" style={{color: 'rgba(0,0,0,.25)'}}/>}
placeholder="用户名"
/>,
)}
</Form.Item>
<Form.Item>
{getFieldDecorator('password', {
rules: [{required: true, message: '请输入密码!'}],
})(
<Input
prefix={<Icon type="lock" style={{color: 'rgba(0,0,0,.25)'}}/>}
type="password"
placeholder="密码"
/>,
)}
</Form.Item>
<Form.Item>
{getFieldDecorator('remember', {
valuePropName: 'checked',
initialValue: true,
})(<Checkbox>记住密码</Checkbox>)}
<a className="login-form-forgot" href="">
忘记密码
</a>
<Button
type="primary"
htmlType="submit"
className="login-form-button"
loading={loading}
>
登录
</Button>
Or <a href="">现在注册</a>
</Form.Item>
</Form>
</div>
);
}
}
function mapStateToProps(state: any) {
return {
loading: state.loading.effects['admin/login']
};
}
const WrappedNormalLoginForm = Form.create({name: 'normal_login'})(LoginPage);
export default connect(mapStateToProps)(WrappedNormalLoginForm)