对接注册和退出登录

This commit is contained in:
linxd
2025-07-17 11:38:51 +08:00
parent baedd70f53
commit 46c9a0f679
18 changed files with 524 additions and 196 deletions

View File

@ -1,13 +1,18 @@
export default { export default {
dev: { dev: {
// '/api/wfap/v1/audit/bill/find/by/procid': { '/api/v1': {
// target: 'http://10.242.37.148:8891/',//审批单 dev环境自动审批暂时用不到 target: 'http://10.0.0.10:18030',// 茂
// changeOrigin: true, // target: 'http://10.0.0.14:18030',// 李
// pathRewrite: { '^': '' }, changeOrigin: true,
// }, pathRewrite: { '^/api/v1': '/v1' },
},
'/api': { '/api': {
// target: 'http://10.0.0.125:18012',//测试环境 // target: 'http://10.242.37.148:18022',//
target: 'http://10.0.0.10:18013',//连接天宫的ng target: 'http://10.0.0.10:18013',//
// target: 'http://10.0.0.125:18012',// 测试
// target: 'http://10.0.0.14:18012',// 李
// target: 'http://10.0.0.14:18030',// 李
// target: 'http://10.0.0.46:18013',// 袁
changeOrigin: true, changeOrigin: true,
pathRewrite: { '^/api': '' }, pathRewrite: { '^/api': '' },
}, },

View File

@ -70,9 +70,11 @@
"array-move": "3.0.1", "array-move": "3.0.1",
"axios": "0.21.1", "axios": "0.21.1",
"classnames": "2.3.1", "classnames": "2.3.1",
"dayjs": "^1.11.13",
"dva": "2.4.1", "dva": "2.4.1",
"echarts": "^5.2.2", "echarts": "^5.2.2",
"echarts-for-react": "^3.0.2", "echarts-for-react": "^3.0.2",
"jsencrypt": "^3.3.2",
"lodash": "4.17.21", "lodash": "4.17.21",
"moment": "^2.29.4", "moment": "^2.29.4",
"omit.js": "2.0.2", "omit.js": "2.0.2",

10
pnpm-lock.yaml generated
View File

@ -50,6 +50,9 @@ dependencies:
classnames: classnames:
specifier: 2.3.1 specifier: 2.3.1
version: 2.3.1 version: 2.3.1
dayjs:
specifier: ^1.11.13
version: 1.11.13
dva: dva:
specifier: 2.4.1 specifier: 2.4.1
version: 2.4.1(react-dom@16.14.0)(react@16.14.0) version: 2.4.1(react-dom@16.14.0)(react@16.14.0)
@ -59,6 +62,9 @@ dependencies:
echarts-for-react: echarts-for-react:
specifier: ^3.0.2 specifier: ^3.0.2
version: 3.0.2(echarts@5.6.0)(react@16.14.0) version: 3.0.2(echarts@5.6.0)(react@16.14.0)
jsencrypt:
specifier: ^3.3.2
version: 3.3.2
lodash: lodash:
specifier: 4.17.21 specifier: 4.17.21
version: 4.17.21 version: 4.17.21
@ -10263,6 +10269,10 @@ packages:
- utf-8-validate - utf-8-validate
dev: true dev: true
/jsencrypt@3.3.2:
resolution: {integrity: sha512-arQR1R1ESGdAxY7ZheWr12wCaF2yF47v5qpB76TtV64H1pyGudk9Hvw8Y9tb/FiTIaaTRUyaSnm5T/Y53Ghm/A==}
dev: false
/jsesc@2.5.2: /jsesc@2.5.2:
resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==} resolution: {integrity: sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==}
engines: {node: '>=4'} engines: {node: '>=4'}

View File

@ -1,18 +1,74 @@
import React from 'react'; import React, { useEffect } from 'react';
//导入logo图片 //导入logo图片
import LogoImg from '@/assets/img/logo.png'; import LogoImg from '@/assets/img/logo.png';
// 引入样式文件 // 引入样式文件
import './Header.less'; import './Header.less';
//导入菜单组件 //导入菜单组件
import HeaderMenu from './HeaderMenu'; import HeaderMenu from './HeaderMenu';
const Header: React.FC = (props) => { import { connect, history } from 'umi';
import type { UserModelState } from '@/models/user';
import type { ConnectProps, Dispatch } from 'umi';
import { Button, Dropdown, message, Modal } from 'antd';
import { DownOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
interface PageProps extends ConnectProps {
user: UserModelState; // dva model状态
dispatch: Dispatch; // dva dispatch方法
}
const Header: React.FC<PageProps> = ({ user, dispatch }) => {
useEffect(() => {
if (user.token) {
dispatch({
type: 'user/fetchUserInfo',
});
}
}, [user.token]);
const handleMenuClick = (e: any) => {
if (e.key === 'logout') {
Modal.confirm({
title: '确定退出登录吗?',
icon: <ExclamationCircleOutlined />,
content: '退出登录后,您将需要重新登录',
onOk() {
dispatch({
type: 'user/logout',
}).then(() => {
sessionStorage.clear();
message.success('退出登录成功');
history.push('/index');
});
},
onCancel() {
return;
},
});
}
};
return ( return (
<div className="header-container"> <div className="header-container">
<div className="header"> <div className="header">
<img className="logo" src={LogoImg} alt="logo" /> <img className="logo" src={LogoImg} alt="logo" />
<HeaderMenu /> <HeaderMenu />
{user.userInfo?.fullName && (
<Dropdown
trigger={['hover']}
menu={{
items: [
{
key: 'logout',
label: '退出登录',
},
],
onClick: handleMenuClick,
}}
>
<Button type="link">
{`${user.userInfo?.fullName}`}
<DownOutlined />
</Button>
</Dropdown>
)}
</div> </div>
</div> </div>
); );
}; };
export default Header; export default connect(({ user }: { user: UserModelState }) => ({ user }))(Header);

View File

@ -4,6 +4,7 @@ import help from './en-US/help';
import policy from './en-US/policy'; import policy from './en-US/policy';
import register from './en-US/register'; import register from './en-US/register';
import home from './en-US/home'; import home from './en-US/home';
import login from './en-US/login';
export default { export default {
'menu.首页': 'Home', 'menu.首页': 'Home',
@ -39,19 +40,6 @@ export default {
"加载更多":"Load More", "加载更多":"Load More",
"登录/注册":"Login/Register", "登录/注册":"Login/Register",
// Login page
"login.title": "E-Bidding Platform",
"login.tab.supplier": "Supplier",
"login.tab.expert": "Expert",
"login.tab.agent": "Bidding Agent",
"login.username.placeholder": "Please enter username",
"login.password.placeholder": "Please enter password",
"login.remember": "Remember password",
"login.forgot": "Forgot password?",
"login.button": "Login",
"login.register.tip": "Don't have an account?",
"login.register.action": "Register Now",
"login.back.home": "Back to Home",
// Help Center module // Help Center module
...help, ...help,
@ -70,4 +58,7 @@ export default {
// Home module // Home module
...home, ...home,
// Login page
...login,
}; };

View File

@ -0,0 +1,16 @@
export default {
// Login page
"login.title": "E-Bidding Platform",
"login.tab.supplier": "Supplier",
"login.tab.expert": "Expert",
"login.tab.agent": "Bidding Agent",
"login.username.placeholder": "Please enter username",
"login.password.placeholder": "Please enter password",
"login.remember": "Remember me",
"login.forgot": "Forgot password?",
"login.button": "Login",
"login.register.tip": "Don't have an account?",
"login.register.action": "Register now",
"login.back.home": "Back to home",
"login.captcha.placeholder": "Please enter verification code",
};

View File

@ -4,6 +4,7 @@ import help from './zh-CN/help';
import policy from './zh-CN/policy'; import policy from './zh-CN/policy';
import register from './zh-CN/register'; import register from './zh-CN/register';
import home from './zh-CN/home'; import home from './zh-CN/home';
import login from './zh-CN/login';
export default { export default {
'menu.首页': '首页', 'menu.首页': '首页',
@ -39,19 +40,6 @@ export default {
"加载更多":"加载更多", "加载更多":"加载更多",
"登录/注册":"登录/注册", "登录/注册":"登录/注册",
// 登录页文案
"login.title": "电子招投标平台",
"login.tab.supplier": "供应商",
"login.tab.expert": "专家",
"login.tab.agent": "招标代理",
"login.username.placeholder": "请输入用户名",
"login.password.placeholder": "请输入密码",
"login.remember": "记住密码",
"login.forgot": "忘记密码?",
"login.button": "登录",
"login.register.tip": "还没有账号?",
"login.register.action": "立即注册",
"login.back.home": "返回首页",
// 帮助中心模块 // 帮助中心模块
...help, ...help,
@ -70,4 +58,7 @@ export default {
// Home module // Home module
...home, ...home,
// Login page
...login,
}; };

View File

@ -0,0 +1,16 @@
export default {
// 登录页文案
"login.title": "中远海运",
"login.tab.supplier": "供应商",
"login.tab.expert": "专家",
"login.tab.agent": "招标代理",
"login.username.placeholder": "请输入用户名",
"login.password.placeholder": "请输入密码",
"login.remember": "记住密码",
"login.forgot": "忘记密码?",
"login.button": "登录",
"login.register.tip": "还没有账号?",
"login.register.action": "立即注册",
"login.back.home": "返回首页",
"login.captcha.placeholder": "请输入验证码",
};

4
src/models/index.ts Normal file
View File

@ -0,0 +1,4 @@
import user from './user';
export default {
user,
};

103
src/models/user.ts Normal file
View File

@ -0,0 +1,103 @@
import type { Effect, Reducer } from 'umi';
import { getUserinfo, Logout } from '@/servers/api/login';
export type SupplierUser = {
email?: string;
mobile?: string;
name?: string;
sex?: string;
status?: number;
userId?: string;
userName?: string;
};
export type User = {
userId: string;
userName: string;
userType: string;
};
export type UserInfo = {
userId: string;
fullName: string;
loginName: string;
userType: string;
authorityList: { roleId: string, roleName: string, roleCode: string, roleScope: string }[];
};
export type UserModelState = {
supplierUser?: SupplierUser;
user?: User;
token?: string;
userInfo?: UserInfo;
};
export type UserModelType = {
namespace: 'user';
state: UserModelState;
effects: {
fetchUserInfo: Effect;
logout: Effect;
};
reducers: {
saveLoginUser: Reducer<UserModelState>;
saveUserInfo: Reducer<UserModelState>;
clearUserInfo: Reducer;
};
};
const UserModel: UserModelType = {
namespace: 'user',
state: {
supplierUser: {}, // 登录返回的供应商用户信息
user: undefined, // 登录返回的用户信息
token: sessionStorage.getItem('token') || '', // 登录返回的token
userInfo: undefined, // 请求userInfo返回的用户信息
},
effects: {
*fetchUserInfo(_, { call, put }) {
const response = yield call(getUserinfo);
yield put({
type: 'saveUserInfo',
payload: response,
});
},
*logout(_, { call, put }) {
yield call(Logout);
yield put({
type: 'clearUserInfo'
});
},
},
reducers: {
clearUserInfo(state) {
return {
...state,
userInfo: undefined,
token: '',
supplierUser: {},
user: {},
};
},
saveLoginUser(state, action) {
return {
...state,
supplierUser: action.payload.supplierUser || {},
user: action.payload.user || {},
token: action.payload.token || '',
};
},
saveUserInfo(state, action) {
return {
...state,
userInfo: action.payload || {},
};
},
}
};
export default UserModel;

View File

@ -31,70 +31,43 @@
} }
.noticeList { .noticeList {
padding: 10px 0; padding: 10px 0;
.noticeTitle { display: flex;
font-size: 22px; gap: 20px;
align-items: center;
.noticeName {
font-size: 18px;
font-weight: 600; font-weight: 600;
color: @main-color; color: @main-danger-color;
padding: 10px 7px; width: 50px;
line-height: 20px;
text-align: center;
} }
.noticeItem { .noticeItem {
background-color: #fff; margin-right: 20px;
padding: 24px; flex: 1;
border-radius: 16px; width: 0;
box-shadow: 0px 1px 4px 0px rgba(0, 0, 0, 0.05); .cardTitle {
height: 249px;
.title {
margin: 14px 0;
font-size: 20px;
font-weight: 600;
// 超过两行显示省略号
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
height: 63px;
}
.content{
font-size: 16px;
color: @main-text-color-2;
// 超过两行显示省略号
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
.button{
color: @main-color;
font-size: 16px;
margin-top: 14px;
cursor: pointer;
}
.header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; .cardTitleText {
.time { font-size: 18px;
font-size: 14px; font-weight: 600;
color: @main-color;
cursor: pointer;
&:hover {
text-decoration: underline;
}
}
.cardTitleTime {
color: @main-text-color-2; color: @main-text-color-2;
} }
.type { }
display: inline-block; .cardContent {
background-color: @main-color; color: @main-text-color-2;
color: #fff; text-overflow: ellipsis;
font-size: 12px; overflow: hidden;
padding: 4px 8px; white-space: nowrap;
border-radius: 20px; margin-bottom: 0;
&.primary {
background-color: rgba(59, 130, 246, 0.1);
color: #3b82f6;
}
&.danger {
background-color: rgba(255, 77, 77, 0.1);
color: #ff4d4d;
}
}
} }
} }
} }
@ -163,7 +136,7 @@
} }
} }
} }
.search { .search{
height: 100%; height: 100%;
} }
} }

View File

@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { Card, Row, Col, Tabs, Table, Button, Space } from 'antd'; import { Card, Row, Col, Tabs, Table, Button, Space } from 'antd';
import { useIntl, Link, history } from 'umi'; import { useIntl, Link, history, connect } from 'umi';
import './index.less'; import './index.less';
import IconFont from '@/components/IconFont/IconFont'; import IconFont from '@/components/IconFont/IconFont';
import Search from '../announce/Search'; import Search from '../announce/Search';
@ -12,7 +12,8 @@ import { getAboutUs } from '@/servers/api/about';
// 获取通知公告 // 获取通知公告
import { getNoticeList } from '@/servers/api/notice'; import { getNoticeList } from '@/servers/api/notice';
import { filterHtmlTag } from '@/utils/utils'; import { filterHtmlTag } from '@/utils/utils';
const IndexPage: React.FC = () => { const IndexPage: React.FC<any> = ({ user }) => {
const token = user.token;
const intl = useIntl(); const intl = useIntl();
const [noticeLoading, setNoticeLoading] = useState(false); const [noticeLoading, setNoticeLoading] = useState(false);
// 友情链接 // 友情链接
@ -186,7 +187,29 @@ const IndexPage: React.FC = () => {
return ( return (
<div> <div>
<img className="banner" src={require('@/assets/img/banner.jpg')} alt="" /> <img className="banner" src={require('@/assets/img/banner.jpg')} alt="" />
{/* 通知列表 */}
<div className="noticeList layout-content-main">
<div className="noticeName"></div>
{noticeList.map((item) => (
<div className="noticeItem" key={item.id}>
<div className="cardTitle">
<span
className="cardTitleText"
onClick={() => {
history.push({
pathname: '/announce/announceInfo',
search: '?id=' + item.id,
});
}}
>
{item.title}
</span>
<span className="cardTitleTime">{item.publishTime}</span>
</div>
<p className="cardContent" dangerouslySetInnerHTML={{ __html: item.content }} />
</div>
))}
</div>
<div className="loginType"> <div className="loginType">
<div className="layout-content-main"> <div className="layout-content-main">
<Row gutter={20}> <Row gutter={20}>
@ -242,49 +265,6 @@ const IndexPage: React.FC = () => {
</div> </div>
</div> </div>
{/* 通知列表 */}
<div className="noticeList">
<div className="layout-content-main">
<div className="noticeTitle">
</div>
<Row gutter={32}>
{noticeList.map((item) => (
<Col span={6} key={item.id}>
<div className="noticeItem" >
<div className='header'>
<span className='time'>{item.publishTime}</span>
<span className={`type ${item.columnType === 'notice' ? 'primary' : 'danger'}`}>{item.columnType === 'notice' ? '公告' : '通知'}</span>
</div>
<div className='title'>{item[intl.formatMessage({ id: 'notice.data.title' }) as keyof typeof item]}</div>
<div className='content'>{filterHtmlTag(item[intl.formatMessage({ id: 'notice.data.content' }) as keyof typeof item] || '')}</div>
<div className="button" onClick={() => {
history.push({
pathname: '/announce/announceInfo',
search: '?id=' + item.id,
});
}}>{intl.formatMessage({ id: 'notice.detail' })}&gt;</div>
{/* <div className="cardTitle">
<span
className="cardTitleText"
onClick={() => {
history.push({
pathname: '/announce/announceInfo',
search: '?id=' + item.id,
});
}}
>
{item.title}
</span>
<span className="cardTitleTime">{item.time}</span>
</div> */}
{/* <p className="cardContent">{item.content}</p> */}
</div>
</Col>
))}
</Row>
</div>
</div>
<div className="layout-content-main announce"> <div className="layout-content-main announce">
<Row gutter={40}> <Row gutter={40}>
<Col span={5}> <Col span={5}>
@ -393,4 +373,6 @@ const IndexPage: React.FC = () => {
); );
}; };
export default IndexPage; export default connect(({ user }: any) => ({
user,
}))(IndexPage);

View File

@ -1,26 +1,57 @@
import React, { useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Form, Input, Button, Checkbox, Tabs, message } from 'antd'; import { Form, Input, Button, Checkbox, Tabs, message } from 'antd';
import { UserOutlined, LockOutlined, EyeInvisibleOutlined, EyeTwoTone, HomeOutlined } from '@ant-design/icons'; import {
import { history, useIntl } from 'umi'; UserOutlined,
LockOutlined,
EyeInvisibleOutlined,
EyeTwoTone,
HomeOutlined,
} from '@ant-design/icons';
import { history, useIntl, connect } from 'umi';
import type { ConnectProps, Dispatch } from 'umi';
import './login.less'; import './login.less';
import { getCaptcha, supplierLogin, getUserinfo, findMenuList } from '@/servers/api/login';
import { encryptWithRsa } from '@/utils/encryptWithRsa';
import type { UserModelState } from '@/models/user';
const { TabPane } = Tabs; const { TabPane } = Tabs;
interface PageProps extends ConnectProps {
const LoginPage: React.FC = () => { user: UserModelState; // dva model状态
dispatch: Dispatch; // dva dispatch方法
}
const LoginPage: React.FC<PageProps> = ({ user, dispatch }) => {
const [activeKey, setActiveKey] = useState('supplier'); const [activeKey, setActiveKey] = useState('supplier');
const [form] = Form.useForm(); const [form] = Form.useForm();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const intl = useIntl(); const intl = useIntl();
const [captchaImg, setCaptchaImg] = useState('');
const onFinish = (values: any) => { const [captchaKey, setCaptchaKey] = useState('');
const onFinish = async (values: any) => {
setLoading(true); setLoading(true);
console.log('登录信息:', values); try {
// 这里添加登录逻辑 const params = {
setTimeout(() => { ...values,
password: encryptWithRsa(values.password, false),
encryptValue: encryptWithRsa(values.identifying),
};
const loginRes = await supplierLogin(params);
if (loginRes.code === 200) {
sessionStorage.setItem('token', loginRes.data.token);
//存入供应商用户id
sessionStorage.setItem('userId', loginRes.data.supplierUser.userId);
sessionStorage.setItem('currentUser', JSON.stringify(loginRes.data));
dispatch({
type: 'user/saveLoginUser',
payload: loginRes.data,
})
message.success('登录成功');
history.push('/index');
} else {
message.error(loginRes.message || '登录失败');
}
} finally {
setLoading(false); setLoading(false);
message.success('登录成功'); }
history.push('/index');
}, 1000);
}; };
const handleTabChange = (key: string) => { const handleTabChange = (key: string) => {
@ -30,7 +61,7 @@ const LoginPage: React.FC = () => {
// 根据当前选中的Tab决定跳转到哪个注册页面 // 根据当前选中的Tab决定跳转到哪个注册页面
const handleRegister = () => { const handleRegister = () => {
switch(activeKey) { switch (activeKey) {
case 'supplier': case 'supplier':
history.push('/register/supplier'); history.push('/register/supplier');
break; break;
@ -52,42 +83,55 @@ const LoginPage: React.FC = () => {
return ( return (
<div className="register-link"> <div className="register-link">
{intl.formatMessage({ id: 'login.register.tip' })} {intl.formatMessage({ id: 'login.register.tip' })}
<a onClick={handleRegister}> <a onClick={handleRegister}>{intl.formatMessage({ id: 'login.register.action' })}</a>
{intl.formatMessage({ id: 'login.register.action' })}
</a>
</div> </div>
); );
}; };
// 获取验证码
const fetchCaptcha = async () => {
const res = await getCaptcha();
if (res.code === 200) {
setCaptchaImg(res.data.base64Image);
setCaptchaKey(res.data.code);
}
};
useEffect(() => {
fetchCaptcha();
}, [activeKey]);
return ( return (
<div className='login-page'> <div className="login-page">
<div className='login-container'> <div className="login-container">
<div className='back-home'> <div className="back-home">
<a onClick={() => history.push('/index')}> <a onClick={() => history.push('/index')}>
<HomeOutlined /> {intl.formatMessage({ id: 'login.back.home' })} <HomeOutlined /> {intl.formatMessage({ id: 'login.back.home' })}
</a> </a>
</div> </div>
<div className='login-title'>{intl.formatMessage({ id: 'login.title' })}</div> <div className="login-title">{intl.formatMessage({ id: 'login.title' })}</div>
<div className="login-tab-container"> {/* <div className="login-tab-container">
<Tabs activeKey={activeKey} onChange={handleTabChange} className='login-tabs'> <Tabs activeKey={activeKey} onChange={handleTabChange} className='login-tabs'>
<TabPane tab={intl.formatMessage({ id: 'login.tab.supplier' })} key="supplier" /> <TabPane tab={intl.formatMessage({ id: 'login.tab.supplier' })} key="supplier" />
<TabPane tab={intl.formatMessage({ id: 'login.tab.expert' })} key="expert" /> <TabPane tab={intl.formatMessage({ id: 'login.tab.expert' })} key="expert" />
<TabPane tab={intl.formatMessage({ id: 'login.tab.agent' })} key="agent" /> <TabPane tab={intl.formatMessage({ id: 'login.tab.agent' })} key="agent" />
</Tabs> </Tabs>
</div> </div> */}
<Form <Form
form={form} form={form}
name="login" name="login"
className='login-form' className="login-form"
initialValues={{ remember: false }} initialValues={{ remember: false }}
onFinish={onFinish} onFinish={onFinish}
> >
<Form.Item <Form.Item
name="username" name="account"
rules={[{ required: true, message: intl.formatMessage({ id: 'login.username.placeholder' }) + '!' }]} rules={[
{
required: true,
message: intl.formatMessage({ id: 'login.username.placeholder' }) + '!',
},
]}
> >
<Input <Input
prefix={<UserOutlined className="site-form-item-icon" />} prefix={<UserOutlined className="site-form-item-icon" />}
@ -98,18 +142,48 @@ const LoginPage: React.FC = () => {
<Form.Item <Form.Item
name="password" name="password"
rules={[{ required: true, message: intl.formatMessage({ id: 'login.password.placeholder' }) + '!' }]} rules={[
{
required: true,
message: intl.formatMessage({ id: 'login.password.placeholder' }) + '!',
},
]}
> >
<Input.Password <Input.Password
prefix={<LockOutlined className="site-form-item-icon" />} prefix={<LockOutlined className="site-form-item-icon" />}
placeholder={intl.formatMessage({ id: 'login.password.placeholder' })} placeholder={intl.formatMessage({ id: 'login.password.placeholder' })}
iconRender={visible => (visible ? <EyeTwoTone /> : <EyeInvisibleOutlined />)} iconRender={(visible) => (visible ? <EyeTwoTone /> : <EyeInvisibleOutlined />)}
size="large" size="large"
/> />
</Form.Item> </Form.Item>
<Form.Item
name="identifying"
rules={[
{
required: true,
message: intl.formatMessage({ id: 'login.captcha.placeholder' }) + '!',
},
]}
>
<Input
placeholder={intl.formatMessage({ id: 'login.captcha.placeholder' })}
size="large"
maxLength={6}
autoComplete="off"
prefix={null}
suffix={
<img
src={`data:image/png;base64,${captchaImg}`}
alt="验证码"
style={{ cursor: 'pointer', height: 32, verticalAlign: 'middle' }}
onClick={fetchCaptcha}
/>
}
/>
</Form.Item>
<Form.Item> <Form.Item>
<div className='login-options'> <div className="login-options">
<Form.Item name="remember" valuePropName="checked" noStyle> <Form.Item name="remember" valuePropName="checked" noStyle>
<Checkbox>{intl.formatMessage({ id: 'login.remember' })}</Checkbox> <Checkbox>{intl.formatMessage({ id: 'login.remember' })}</Checkbox>
</Form.Item> </Form.Item>
@ -120,7 +194,13 @@ const LoginPage: React.FC = () => {
</Form.Item> </Form.Item>
<Form.Item> <Form.Item>
<Button type="primary" htmlType="submit" className="login-form-button" loading={loading} size="large"> <Button
type="primary"
htmlType="submit"
className="login-form-button"
loading={loading}
size="large"
>
{intl.formatMessage({ id: 'login.button' })} {intl.formatMessage({ id: 'login.button' })}
</Button> </Button>
{renderRegisterLink()} {renderRegisterLink()}
@ -131,4 +211,4 @@ const LoginPage: React.FC = () => {
); );
}; };
export default LoginPage; export default connect(({ user }: { user: UserModelState }) => ({ user }))(LoginPage);

73
src/servers/api/login.ts Normal file
View File

@ -0,0 +1,73 @@
import request from '@/utils/request';
/**
* 验证码
*/
export async function getCaptcha() {
return request('/v1/login/getCaptcha', {
method: 'GET'
});
}
/**
* 招标代理
*/
export async function accountLogin (data: API.LoginSupplier) {
return request('/v1/login/accountLogin', {
method: 'POST',
data
});
}
/**
* 专家
*/
export async function expertLogin (data: API.LoginSupplier) {
return request('/v1/login/expertLogin', {
method: 'POST',
data
});
}
/**
* 供应商
*/
export async function supplierLogin (data: API.LoginSupplier) {
return request('/v1/login/accountLogin/supplier', {
method: 'POST',
data
});
}
/**
* 用户信息
*/
export async function getUserinfo() {
return request('/v1/userinfo/get', {
method: 'GET'
});
}
/**
* 退出
*/
export async function Logout() {
return request('/v1/login/logout', {
method: 'POST'
});
}
/**
* 路由
*/
export async function findMenuList(data: any) {
return request('/v1/menu/findMenuList', {
method: 'POST',
data
});
}
/**
* 部门
*/
export async function queryUserOrgAll() {
return request('/org/queryUserOrgAll', {
method: 'GET'
});
}

View File

@ -368,4 +368,11 @@ declare namespace API {
thumbnail: string; thumbnail: string;
url: string; url: string;
} }
//登录
interface LoginSupplier {
account: string;
password: string;
identifying: string;
encryptValue: string;
}
} }

View File

@ -0,0 +1,21 @@
import JSEncrypt from 'jsencrypt';
import dayjs from 'dayjs';
const PUBLIC_KEY = `-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvLBkALIYR/x9Rv5TiXQGWAXTzraN/He80r9gQovSQ5oTP8qllL9+Oc1LdTijPFRsddHWg37umvFliwhmukU1NT+o2loGcKpyMHFkc/UPNjQLvd+YFR4nYhgP8l+dmRNOtQWawOt5dbksRKTghMjA+FKT2+itMsawSs1+Ic+zoIwIDAQAB
-----END PUBLIC KEY-----`;
export function encryptWithRsa(value: string, type: boolean = true, publicKey: string = PUBLIC_KEY): string {
const nowStr = dayjs().format('YYYY-MM-DD HH:mm:ss');
const content = type? `${value}_${nowStr}`: value;
const encryptor = new JSEncrypt();
encryptor.setPublicKey(publicKey);
const encrypted = encryptor.encrypt(content);
if (!encrypted) {
throw new Error('RSA 加密失败');
}
return encrypted;
}

View File

@ -57,24 +57,20 @@ const request = extend({
credentials: 'include' // 默认请求是否带上cookie credentials: 'include' // 默认请求是否带上cookie
}); });
// request拦截器, 改变url 或 options. // request拦截器, 改变url 或 options.
request.interceptors.request.use(async (url, options) => { request.interceptors.request.use((url: string, options: any) => {
if ( // 获取 token
options.method === 'post' || const token = sessionStorage.getItem('token');
options.method === 'put' || const userId = sessionStorage.getItem('userId');
options.method === 'delete' ||
options.method === 'get'
) {
//如果是获取token的url则不加token
// headers = {
// Authorization: getUserToken() == null ? null : getUserToken(),
// JwtToken: getSessionUserData() == null ? null : getSessionUserData().userId,
// ...options.headers
// }
// options.headers = headers;
}
return { return {
url, url,
options: { ...options }, options: {
...options,
headers: {
...(options.headers || {}),
...(token ? { Authorization: `Bearer ${token}` } : {}),
...(userId ? { Mall3Check: `${userId}` } : {}),
},
},
}; };
}); });
//response拦截 //response拦截

View File

@ -1,13 +1,15 @@
import React from 'react'; import React from 'react';
import { Redirect } from 'umi'; import { connect, Redirect, history } from 'umi';
import { message } from 'antd'; import { message, Modal } from 'antd';
// 权限校验 // 权限校验
export default (props: any) => { const AuthWrapper = ({ user, children }: any) => {
if (localStorage.getItem('token')) { if (user.token) {
return <div>{props.children}</div>; return <div>{children}</div>;
} else { } else {
// 提示后跳转
message.error('请先登录');
return <Redirect to="/login" />; return <Redirect to="/login" />;
} }
}; };
export default connect(({ user }: any) => ({
user,
}))(AuthWrapper);