diff --git a/.umirc.ts b/.umirc.ts index 0656a95..1448620 100644 --- a/.umirc.ts +++ b/.umirc.ts @@ -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; diff --git a/package.json b/package.json index 6ff80b6..debddf9 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/data/interface/dva.interface.ts b/src/data/interface/dva.interface.ts new file mode 100644 index 0000000..99a462e --- /dev/null +++ b/src/data/interface/dva.interface.ts @@ -0,0 +1,16 @@ + +export interface Action { + type: string, + data: T +} + +export interface Operators { + call: any, + put: any, + select: any, +} + + +export interface DispatchProps { + dispatch: any +} diff --git a/src/data/interface/response.interface.ts b/src/data/interface/response.interface.ts new file mode 100644 index 0000000..7fbce51 --- /dev/null +++ b/src/data/interface/response.interface.ts @@ -0,0 +1,6 @@ +export interface BaseResponseBody { + statusCode: number; + error: string; + message: string; + data: T; +} diff --git a/src/data/network/api.ts b/src/data/network/api.ts new file mode 100644 index 0000000..434808c --- /dev/null +++ b/src/data/network/api.ts @@ -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(type: string, url: string, data: any = {}, config: any = {}): Promise { + // 统一添加相应的头部 + 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) => { + 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(url: string, data: any, config: any = {}): Promise { + return API.easyRequest('get', url, data, config); + }, + eastPost(url: string, data: any, config: any = {}): Promise { + return API.easyRequest('post', url, data, config); + }, + + //////////////////////////////////////////////////////////// + ///////// 用户相关接口 + //////////////////////////////////////////////////////////// + login: (username: string, password: string): Promise => { + return API.eastPost('/api/login', { + username, + password + }) + } +}; + diff --git a/src/data/network/status-code.ts b/src/data/network/status-code.ts new file mode 100644 index 0000000..92e4820 --- /dev/null +++ b/src/data/network/status-code.ts @@ -0,0 +1,6 @@ +export const StatusCode = { + SUCCESS: 0, // 成功 + COMMENT_ERROR: -1, // 通用错误 + NEED_LOGIN: -2, // 需要登录 + NEED_ADMIN_PERMISSION: -3, // 需要管理员权限 +}; diff --git a/src/data/user.ts b/src/data/user.ts new file mode 100644 index 0000000..4d9f1b1 --- /dev/null +++ b/src/data/user.ts @@ -0,0 +1,8 @@ +export class User { + id: number = -1; + username: string = ''; + password: string = ''; + avatar: string = ''; + nickName: string = ''; + age: number = 0; +} diff --git a/src/layouts/index.tsx b/src/layouts/index.tsx index e3ea779..8f803f1 100644 --- a/src/layouts/index.tsx +++ b/src/layouts/index.tsx @@ -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 { 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 { } render(): React.ReactElement> | 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 (
{this.props.children} @@ -71,4 +80,8 @@ class BasicLayout extends React.Component { } } -export default withRouter(BasicLayout); +function mapStateToProps(state: any) { + return state; +} + +export default connect(mapStateToProps)(withRouter(BasicLayout)); diff --git a/src/manager/storage.manager.ts b/src/manager/storage.manager.ts new file mode 100644 index 0000000..93dc535 --- /dev/null +++ b/src/manager/storage.manager.ts @@ -0,0 +1,22 @@ +export const LocalStorageKeys = { + USER: 'user' +}; + +export const LocalStorageManager = { + save(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; + } +}; diff --git a/src/models/admin/index.ts b/src/models/admin/index.ts new file mode 100644 index 0000000..e3eea66 --- /dev/null +++ b/src/models/admin/index.ts @@ -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) { + console.log('fuck?', action); + return { + ...state, + user: action.data + } + } + }, + effects: { + * init(action: Action, 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, 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'); + } + } +} + + diff --git a/src/pages/admin/_layout.tsx b/src/pages/admin/_layout.tsx index 300678a..1e8de24 100644 --- a/src/pages/admin/_layout.tsx +++ b/src/pages/admin/_layout.tsx @@ -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 { const { collapsed } = this.state; + const { + user + } = this.props; const menu = ( - 退出登录 + { + router.replace('/login') + }}>退出登录 ); @@ -89,7 +96,6 @@ class AdminLayout extends React.Component {
@@ -97,9 +103,8 @@ class AdminLayout extends React.Component { className='admin-layout__header_avatar' src='https://avatars2.githubusercontent.com/u/29176413?s=400&u=f78127887499acf06545f9cbe298ce388201dfed&v=4' /> - SunnyQjm + {user && user.username} -
@@ -112,4 +117,10 @@ class AdminLayout extends React.Component { } } -export default withRouter(AdminLayout); +function mapStateToProps(state: any) { + return { + user: state.admin.user + }; +} + +export default connect(mapStateToProps)(withRouter(AdminLayout)); diff --git a/src/pages/login/index.scss b/src/pages/login/index.scss new file mode 100644 index 0000000..11599c4 --- /dev/null +++ b/src/pages/login/index.scss @@ -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%; +} diff --git a/src/pages/login/index.tsx b/src/pages/login/index.tsx new file mode 100644 index 0000000..b9345ae --- /dev/null +++ b/src/pages/login/index.tsx @@ -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 { + 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> | string | number | {} | React.ReactNodeArray | React.ReactPortal | boolean | null | undefined { + const {getFieldDecorator} = this.props.form; + const { + loading + } = this.props; + return ( +
+ + {'建明 | Ming.J'} + + 每一个不曾起舞的日子都是对生命的辜负! +
+ + {getFieldDecorator('username', { + rules: [{required: true, message: '请输入用户名!'}], + })( + } + placeholder="用户名" + />, + )} + + + {getFieldDecorator('password', { + rules: [{required: true, message: '请输入密码!'}], + })( + } + type="password" + placeholder="密码" + />, + )} + + + {getFieldDecorator('remember', { + valuePropName: 'checked', + initialValue: true, + })(记住密码)} + + 忘记密码 + + + Or 现在注册 + +
+
+ ); + } +} + +function mapStateToProps(state: any) { + return { + loading: state.loading.effects['admin/login'] + }; +} + +const WrappedNormalLoginForm = Form.create({name: 'normal_login'})(LoginPage); +export default connect(mapStateToProps)(WrappedNormalLoginForm)