flat:初始化

This commit is contained in:
史典卓
2024-11-18 16:38:38 +08:00
commit 903776f4b9
242 changed files with 24577 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
.avatarHolder {
margin-bottom: 16px;
text-align: center;
position: relative;
display: inline-block;
height: 120px;
& > img {
width: 120px;
height: 120px;
margin-bottom: 20px;
border-radius: 50%;
}
&:hover:after {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
color: #eee;
font-size: 24px;
font-style: normal;
line-height: 110px;
background: rgba(0, 0, 0, 0.5);
border-radius: 50%;
cursor: pointer;
content: '+';
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
}
.teamTitle {
margin-bottom: 12px;
color: @heading-color;
font-weight: 500;
}
.team {
:global {
.ant-avatar {
margin-right: 12px;
}
}
a {
display: block;
margin-bottom: 24px;
overflow: hidden;
color: @text-color;
white-space: nowrap;
text-overflow: ellipsis;
word-break: break-all;
transition: color 0.3s;
&:hover {
color: @primary-color;
}
}
}

View File

@@ -0,0 +1,309 @@
/*!
* Cropper.js v1.5.13
* https://fengyuanchen.github.io/cropperjs
*
* Copyright 2015-present Chen Fengyuan
* Released under the MIT license
*
* Date: 2022-11-20T05:30:43.444Z
*/
.cropper-container {
direction: ltr;
font-size: 0;
line-height: 0;
position: relative;
-ms-touch-action: none;
touch-action: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.cropper-container img {
-webkit-backface-visibility: hidden;
backface-visibility: hidden;
display: block;
height: 100%;
image-orientation: 0deg;
max-height: none !important;
max-width: none !important;
min-height: 0 !important;
min-width: 0 !important;
width: 100%;
}
.cropper-wrap-box,
.cropper-canvas,
.cropper-drag-box,
.cropper-crop-box,
.cropper-modal {
bottom: 0;
left: 0;
position: absolute;
right: 0;
top: 0;
}
.cropper-wrap-box,
.cropper-canvas {
overflow: hidden;
}
.cropper-drag-box {
background-color: #fff;
opacity: 0;
}
.cropper-modal {
background-color: #000;
opacity: 0.5;
}
.cropper-view-box {
display: block;
height: 100%;
outline: 1px solid #39f;
outline-color: rgba(51, 153, 255, 75%);
overflow: hidden;
width: 100%;
}
.cropper-dashed {
border: 0 dashed #eee;
display: block;
opacity: 0.5;
position: absolute;
}
.cropper-dashed.dashed-h {
border-bottom-width: 1px;
border-top-width: 1px;
height: calc(100% / 3);
left: 0;
top: calc(100% / 3);
width: 100%;
}
.cropper-dashed.dashed-v {
border-left-width: 1px;
border-right-width: 1px;
height: 100%;
left: calc(100% / 3);
top: 0;
width: calc(100% / 3);
}
.cropper-center {
display: block;
height: 0;
left: 50%;
opacity: 0.75;
position: absolute;
top: 50%;
width: 0;
}
.cropper-center::before,
.cropper-center::after {
background-color: #eee;
content: " ";
display: block;
position: absolute;
}
.cropper-center::before {
height: 1px;
left: -3px;
top: 0;
width: 7px;
}
.cropper-center::after {
height: 7px;
left: 0;
top: -3px;
width: 1px;
}
.cropper-face,
.cropper-line,
.cropper-point {
display: block;
height: 100%;
opacity: 0.1;
position: absolute;
width: 100%;
}
.cropper-face {
background-color: #fff;
left: 0;
top: 0;
}
.cropper-line {
background-color: #39f;
}
.cropper-line.line-e {
cursor: ew-resize;
right: -3px;
top: 0;
width: 5px;
}
.cropper-line.line-n {
cursor: ns-resize;
height: 5px;
left: 0;
top: -3px;
}
.cropper-line.line-w {
cursor: ew-resize;
left: -3px;
top: 0;
width: 5px;
}
.cropper-line.line-s {
bottom: -3px;
cursor: ns-resize;
height: 5px;
left: 0;
}
.cropper-point {
background-color: #39f;
height: 5px;
opacity: 0.75;
width: 5px;
}
.cropper-point.point-e {
cursor: ew-resize;
margin-top: -3px;
right: -3px;
top: 50%;
}
.cropper-point.point-n {
cursor: ns-resize;
left: 50%;
margin-left: -3px;
top: -3px;
}
.cropper-point.point-w {
cursor: ew-resize;
left: -3px;
margin-top: -3px;
top: 50%;
}
.cropper-point.point-s {
bottom: -3px;
cursor: s-resize;
left: 50%;
margin-left: -3px;
}
.cropper-point.point-ne {
cursor: nesw-resize;
right: -3px;
top: -3px;
}
.cropper-point.point-nw {
cursor: nwse-resize;
left: -3px;
top: -3px;
}
.cropper-point.point-sw {
bottom: -3px;
cursor: nesw-resize;
left: -3px;
}
.cropper-point.point-se {
bottom: -3px;
cursor: nwse-resize;
height: 20px;
opacity: 1;
right: -3px;
width: 20px;
}
@media (min-width: 768px) {
.cropper-point.point-se {
height: 15px;
width: 15px;
}
}
@media (min-width: 992px) {
.cropper-point.point-se {
height: 10px;
width: 10px;
}
}
@media (min-width: 1200px) {
.cropper-point.point-se {
height: 5px;
opacity: 0.75;
width: 5px;
}
}
.cropper-point.point-se::before {
background-color: #39f;
bottom: -50%;
content: " ";
display: block;
height: 200%;
opacity: 0;
position: absolute;
right: -50%;
width: 200%;
}
.cropper-invisible {
opacity: 0;
}
.cropper-bg {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAAA3NCSVQICAjb4U/gAAAABlBMVEXMzMz////TjRV2AAAACXBIWXMAAArrAAAK6wGCiw1aAAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M26LyyjAAAABFJREFUCJlj+M/AgBVhF/0PAH6/D/HkDxOGAAAAAElFTkSuQmCC");
}
.cropper-hide {
display: block;
height: 0;
position: absolute;
width: 0;
}
.cropper-hidden {
display: none !important;
}
.cropper-move {
cursor: move;
}
.cropper-crop {
cursor: crosshair;
}
.cropper-disabled .cropper-drag-box,
.cropper-disabled .cropper-face,
.cropper-disabled .cropper-line,
.cropper-disabled .cropper-point {
cursor: not-allowed;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 B

View File

@@ -0,0 +1,10 @@
.avatarPreview {
position: absolute;
top: 50%;
transform: translate(50%, -50%);
width: 200px;
height: 200px;
border-radius: 50%;
box-shadow: 0 0 4px #ccc;
overflow: hidden;
}

View File

@@ -0,0 +1,144 @@
import React, { useEffect, useRef, useState } from 'react';
import { Modal, Row, Col, Button, Space, Upload, message } from 'antd';
import { useIntl } from '@umijs/max';
import { uploadAvatar } from '@/services/system/user';
import { Cropper } from 'react-cropper';
import './cropper.css';
import styles from './index.less';
import {
MinusOutlined,
PlusOutlined,
RedoOutlined,
UndoOutlined,
UploadOutlined,
} from '@ant-design/icons';
/* *
*
* @author whiteshader@163.com
* @datetime 2022/02/24
*
* */
export type AvatarCropperProps = {
onFinished: (isSuccess: boolean) => void;
open: boolean;
data: any;
};
const AvatarCropperForm: React.FC<AvatarCropperProps> = (props) => {
const cropperRef = useRef<HTMLImageElement>(null);
const [avatarData, setAvatarData] = useState<any>();
const [previewData, setPreviewData] = useState();
useEffect(() => {
setAvatarData(props.data);
}, [props]);
const intl = useIntl();
const handleOk = () => {
const imageElement: any = cropperRef?.current;
const cropper: any = imageElement?.cropper;
cropper.getCroppedCanvas().toBlob((blob: Blob) => {
const formData = new FormData();
formData.append('avatarfile', blob);
uploadAvatar(formData).then((res) => {
if (res.code === 200) {
message.success(res.msg);
props.onFinished(true);
} else {
message.warning(res.msg);
}
});
}, 'image/png');
};
const handleCancel = () => {
props.onFinished(false);
};
const onCrop = () => {
const imageElement: any = cropperRef?.current;
const cropper: any = imageElement?.cropper;
setPreviewData(cropper.getCroppedCanvas().toDataURL());
};
const onRotateRight = () => {
const imageElement: any = cropperRef?.current;
const cropper: any = imageElement?.cropper;
cropper.rotate(90);
};
const onRotateLeft = () => {
const imageElement: any = cropperRef?.current;
const cropper: any = imageElement?.cropper;
cropper.rotate(-90);
};
const onZoomIn = () => {
const imageElement: any = cropperRef?.current;
const cropper: any = imageElement?.cropper;
cropper.zoom(0.1);
};
const onZoomOut = () => {
const imageElement: any = cropperRef?.current;
const cropper: any = imageElement?.cropper;
cropper.zoom(-0.1);
};
const beforeUpload = (file: any) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => {
setAvatarData(reader.result);
};
};
return (
<Modal
width={800}
title={intl.formatMessage({
id: 'system.user.modify_avatar',
defaultMessage: '修改头像',
})}
open={props.open}
destroyOnClose
onOk={handleOk}
onCancel={handleCancel}
>
<Row gutter={[16, 16]}>
<Col span={12} order={1}>
<Cropper
ref={cropperRef}
src={avatarData}
style={{ height: 350, width: '100%', marginBottom: '16px' }}
initialAspectRatio={1}
guides={false}
crop={onCrop}
zoomable={true}
zoomOnWheel={true}
rotatable={true}
/>
</Col>
<Col span={12} order={2}>
<div className={styles.avatarPreview}>
<img src={previewData} style={{ height: '100%', width: '100%' }} />
</div>
</Col>
</Row>
<Row gutter={[16, 16]}>
<Col span={6}>
<Upload beforeUpload={beforeUpload} maxCount={1}>
<Button>
<UploadOutlined />
</Button>
</Upload>
</Col>
<Col>
<Space>
<Button icon={<RedoOutlined />} onClick={onRotateRight} />
<Button icon={<UndoOutlined />} onClick={onRotateLeft} />
<Button icon={<PlusOutlined />} onClick={onZoomIn} />
<Button icon={<MinusOutlined />} onClick={onZoomOut} />
</Space>
</Col>
</Row>
</Modal>
);
};
export default AvatarCropperForm;

View File

@@ -0,0 +1,119 @@
import React from 'react';
import { Form, message, Row } from 'antd';
import { FormattedMessage, useIntl } from '@umijs/max';
import { ProForm, ProFormRadio, ProFormText } from '@ant-design/pro-components';
import { updateUserProfile } from '@/services/system/user';
export type BaseInfoProps = {
values: Partial<API.CurrentUser> | undefined;
};
const BaseInfo: React.FC<BaseInfoProps> = (props) => {
const [form] = Form.useForm();
const intl = useIntl();
const handleFinish = async (values: Record<string, any>) => {
const data = { ...props.values, ...values } as API.CurrentUser;
const resp = await updateUserProfile(data);
if (resp.code === 200) {
message.success('修改成功');
} else {
message.warning(resp.msg);
}
};
return (
<>
<ProForm form={form} onFinish={handleFinish} initialValues={props.values}>
<Row>
<ProFormText
name="nickName"
label={intl.formatMessage({
id: 'system.user.nick_name',
defaultMessage: '用户昵称',
})}
width="xl"
placeholder="请输入用户昵称"
rules={[
{
required: true,
message: (
<FormattedMessage id="请输入用户昵称!" defaultMessage="请输入用户昵称!" />
),
},
]}
/>
</Row>
<Row>
<ProFormText
name="phonenumber"
label={intl.formatMessage({
id: 'system.user.phonenumber',
defaultMessage: '手机号码',
})}
width="xl"
placeholder="请输入手机号码"
rules={[
{
required: false,
message: (
<FormattedMessage id="请输入手机号码!" defaultMessage="请输入手机号码!" />
),
},
]}
/>
</Row>
<Row>
<ProFormText
name="email"
label={intl.formatMessage({
id: 'system.user.email',
defaultMessage: '邮箱',
})}
width="xl"
placeholder="请输入邮箱"
rules={[
{
type: 'email',
message: '无效的邮箱地址!',
},
{
required: false,
message: <FormattedMessage id="请输入邮箱!" defaultMessage="请输入邮箱!" />,
},
]}
/>
</Row>
<Row>
<ProFormRadio.Group
options={[
{
label: '男',
value: '0',
},
{
label: '女',
value: '1',
},
]}
name="sex"
label={intl.formatMessage({
id: 'system.user.sex',
defaultMessage: 'sex',
})}
width="xl"
rules={[
{
required: false,
message: <FormattedMessage id="请输入性别!" defaultMessage="请输入性别!" />,
},
]}
/>
</Row>
</ProForm>
</>
);
};
export default BaseInfo;

View File

@@ -0,0 +1,84 @@
import React from 'react';
import { Form, message } from 'antd';
import { FormattedMessage, useIntl } from '@umijs/max';
import { updateUserPwd } from '@/services/system/user';
import { ProForm, ProFormText } from '@ant-design/pro-components';
const ResetPassword: React.FC = () => {
const [form] = Form.useForm();
const intl = useIntl();
const handleFinish = async (values: Record<string, any>) => {
const resp = await updateUserPwd(values.oldPassword, values.newPassword);
if (resp.code === 200) {
message.success('密码重置成功。');
} else {
message.warning(resp.msg);
}
};
const checkPassword = (rule: any, value: string) => {
const login_password = form.getFieldValue('newPassword');
if (value === login_password) {
return Promise.resolve();
}
return Promise.reject(new Error('两次密码输入不一致'));
};
return (
<>
<ProForm form={form} onFinish={handleFinish}>
<ProFormText.Password
name="oldPassword"
label={intl.formatMessage({
id: 'system.user.old_password',
defaultMessage: '旧密码',
})}
width="xl"
placeholder="请输入旧密码"
rules={[
{
required: true,
message: <FormattedMessage id="请输入旧密码!" defaultMessage="请输入旧密码!" />,
},
]}
/>
<ProFormText.Password
name="newPassword"
label={intl.formatMessage({
id: 'system.user.new_password',
defaultMessage: '新密码',
})}
width="xl"
placeholder="请输入新密码"
rules={[
{
required: true,
message: <FormattedMessage id="请输入新密码!" defaultMessage="请输入新密码!" />,
},
]}
/>
<ProFormText.Password
name="confirmPassword"
label={intl.formatMessage({
id: 'system.user.confirm_password',
defaultMessage: '确认密码',
})}
width="xl"
placeholder="请输入确认密码"
rules={[
{
required: true,
message: (
<FormattedMessage id="请输入确认密码!" defaultMessage="请输入确认密码!" />
),
},
{ validator: checkPassword },
]}
/>
</ProForm>
</>
);
};
export default ResetPassword;

View File

@@ -0,0 +1,200 @@
import {
ClusterOutlined,
MailOutlined,
TeamOutlined,
UserOutlined,
MobileOutlined,
ManOutlined,
} from '@ant-design/icons';
import { Card, Col, Divider, List, Row } from 'antd';
import React, { useState } from 'react';
import styles from './Center.less';
import BaseInfo from './components/BaseInfo';
import ResetPassword from './components/ResetPassword';
import AvatarCropper from './components/AvatarCropper';
import { useRequest } from '@umijs/max';
import { getUserInfo } from '@/services/session';
import { PageLoading } from '@ant-design/pro-components';
const operationTabList = [
{
key: 'base',
tab: (
<span>
</span>
),
},
{
key: 'password',
tab: (
<span>
</span>
),
},
];
export type tabKeyType = 'base' | 'password';
const Center: React.FC = () => {
const [tabKey, setTabKey] = useState<tabKeyType>('base');
const [cropperModalOpen, setCropperModalOpen] = useState<boolean>(false);
// 获取用户信息
const { data: userInfo, loading } = useRequest(async () => {
return { data: await getUserInfo()};
});
if (loading) {
return <div>loading...</div>;
}
const currentUser = userInfo?.user;
// 渲染用户信息
const renderUserInfo = ({
userName,
phonenumber,
email,
sex,
dept,
}: Partial<API.CurrentUser>) => {
return (
<List>
<List.Item>
<div>
<UserOutlined
style={{
marginRight: 8,
}}
/>
</div>
<div>{userName}</div>
</List.Item>
<List.Item>
<div>
<ManOutlined
style={{
marginRight: 8,
}}
/>
</div>
<div>{sex === '1' ? '女' : '男'}</div>
</List.Item>
<List.Item>
<div>
<MobileOutlined
style={{
marginRight: 8,
}}
/>
</div>
<div>{phonenumber}</div>
</List.Item>
<List.Item>
<div>
<MailOutlined
style={{
marginRight: 8,
}}
/>
</div>
<div>{email}</div>
</List.Item>
<List.Item>
<div>
<ClusterOutlined
style={{
marginRight: 8,
}}
/>
</div>
<div>{dept?.deptName}</div>
</List.Item>
</List>
);
};
// 渲染tab切换
const renderChildrenByTabKey = (tabValue: tabKeyType) => {
if (tabValue === 'base') {
return <BaseInfo values={currentUser} />;
}
if (tabValue === 'password') {
return <ResetPassword />;
}
return null;
};
if (!currentUser) {
return <PageLoading />;
}
return (
<div>
<Row gutter={[16, 24]}>
<Col lg={8} md={24}>
<Card
title="个人信息"
bordered={false}
loading={loading}
>
{!loading && (
<div style={{ textAlign: "center"}}>
<div className={styles.avatarHolder} onClick={()=>{setCropperModalOpen(true)}}>
<img alt="" src={currentUser.avatar} />
</div>
{renderUserInfo(currentUser)}
<Divider dashed />
<div className={styles.team}>
<div className={styles.teamTitle}></div>
<Row gutter={36}>
{currentUser.roles &&
currentUser.roles.map((item: any) => (
<Col key={item.roleId} lg={24} xl={12}>
<TeamOutlined
style={{
marginRight: 8,
}}
/>
{item.roleName}
</Col>
))}
</Row>
</div>
</div>
)}
</Card>
</Col>
<Col lg={16} md={24}>
<Card
bordered={false}
tabList={operationTabList}
activeTabKey={tabKey}
onTabChange={(_tabKey: string) => {
setTabKey(_tabKey as tabKeyType);
}}
>
{renderChildrenByTabKey(tabKey)}
</Card>
</Col>
</Row>
<AvatarCropper
onFinished={() => {
setCropperModalOpen(false);
}}
open={cropperModalOpen}
data={currentUser.avatar}
/>
</div>
);
};
export default Center;

View File

@@ -0,0 +1,436 @@
import Footer from '@/components/Footer';
import { getCaptchaImg, login } from '@/services/system/auth';
import { getFakeCaptcha } from '@/services/ant-design-pro/login';
import {
AlipayCircleOutlined,
LockOutlined,
MobileOutlined,
TaobaoCircleOutlined,
UserOutlined,
WeiboCircleOutlined,
} from '@ant-design/icons';
import {
LoginForm,
ProFormCaptcha,
ProFormCheckbox,
ProFormText,
} from '@ant-design/pro-components';
import { useEmotionCss } from '@ant-design/use-emotion-css';
import { FormattedMessage, history, SelectLang, useIntl, useModel, Helmet } from '@umijs/max';
import { Alert, Col, message, Row, Tabs, Image } from 'antd';
import Settings from '../../../../config/defaultSettings';
import React, { useEffect, useState } from 'react';
import { flushSync } from 'react-dom';
import { clearSessionToken, setSessionToken } from '@/access';
const ActionIcons = () => {
const langClassName = useEmotionCss(({ token }) => {
return {
marginLeft: '8px',
color: 'rgba(0, 0, 0, 0.2)',
fontSize: '24px',
verticalAlign: 'middle',
cursor: 'pointer',
transition: 'color 0.3s',
'&:hover': {
color: token.colorPrimaryActive,
},
};
});
return (
<>
<AlipayCircleOutlined key="AlipayCircleOutlined" className={langClassName} />
<TaobaoCircleOutlined key="TaobaoCircleOutlined" className={langClassName} />
<WeiboCircleOutlined key="WeiboCircleOutlined" className={langClassName} />
</>
);
};
const Lang = () => {
const langClassName = useEmotionCss(({ token }) => {
return {
width: 42,
height: 42,
lineHeight: '42px',
position: 'fixed',
right: 16,
borderRadius: token.borderRadius,
':hover': {
backgroundColor: token.colorBgTextHover,
},
};
});
return (
<div className={langClassName} data-lang>
{SelectLang && <SelectLang />}
</div>
);
};
const LoginMessage: React.FC<{
content: string;
}> = ({ content }) => {
return (
<Alert
style={{
marginBottom: 24,
}}
message={content}
type="error"
showIcon
/>
);
};
const Login: React.FC = () => {
const [userLoginState, setUserLoginState] = useState<API.LoginResult>({code: 200});
const [type, setType] = useState<string>('account');
const { initialState, setInitialState } = useModel('@@initialState');
const [captchaCode, setCaptchaCode] = useState<string>('');
const [uuid, setUuid] = useState<string>('');
const containerClassName = useEmotionCss(() => {
return {
display: 'flex',
flexDirection: 'column',
height: '100vh',
overflow: 'auto',
backgroundImage:
"url('https://mdn.alipayobjects.com/yuyan_qk0oxh/afts/img/V-_oS6r-i7wAAAAAAAAAAAAAFl94AQBr')",
backgroundSize: '100% 100%',
};
});
const intl = useIntl();
const getCaptchaCode = async () => {
const response = await getCaptchaImg();
const imgdata = `data:image/png;base64,${response.img}`;
setCaptchaCode(imgdata);
setUuid(response.uuid);
};
const fetchUserInfo = async () => {
const userInfo = await initialState?.fetchUserInfo?.();
if (userInfo) {
flushSync(() => {
setInitialState((s) => ({
...s,
currentUser: userInfo,
}));
});
}
};
const handleSubmit = async (values: API.LoginParams) => {
try {
// 登录
const response = await login({ ...values, uuid });
if (response.code === 200) {
const defaultLoginSuccessMessage = intl.formatMessage({
id: 'pages.login.success',
defaultMessage: '登录成功!',
});
const current = new Date();
const expireTime = current.setTime(current.getTime() + 1000 * 12 * 60 * 60);
console.log('login response: ', response);
setSessionToken(response?.token, response?.token, expireTime);
message.success(defaultLoginSuccessMessage);
await fetchUserInfo();
console.log('login ok');
const urlParams = new URL(window.location.href).searchParams;
history.push(urlParams.get('redirect') || '/');
return;
} else {
console.log(response.msg);
clearSessionToken();
// 如果失败去设置用户错误信息
setUserLoginState({ ...response, type });
getCaptchaCode();
}
} catch (error) {
const defaultLoginFailureMessage = intl.formatMessage({
id: 'pages.login.failure',
defaultMessage: '登录失败,请重试!',
});
console.log(error);
message.error(defaultLoginFailureMessage);
}
};
const { code } = userLoginState;
const loginType = type;
useEffect(() => {
getCaptchaCode();
}, []);
return (
<div className={containerClassName}>
<Helmet>
<title>
{intl.formatMessage({
id: 'menu.login',
defaultMessage: '登录页',
})}
- {Settings.title}
</title>
</Helmet>
<Lang />
<div
style={{
flex: '1',
padding: '32px 0',
}}
>
<LoginForm
contentStyle={{
minWidth: 280,
maxWidth: '75vw',
}}
logo={<img alt="logo" src="/logo.svg" />}
title="Ant Design"
subTitle={intl.formatMessage({ id: 'pages.layouts.userLayout.title' })}
initialValues={{
autoLogin: true,
}}
actions={[
<FormattedMessage
key="loginWith"
id="pages.login.loginWith"
defaultMessage="其他登录方式"
/>,
<ActionIcons key="icons" />,
]}
onFinish={async (values) => {
await handleSubmit(values as API.LoginParams);
}}
>
<Tabs
activeKey={type}
onChange={setType}
centered
items={[
{
key: 'account',
label: intl.formatMessage({
id: 'pages.login.accountLogin.tab',
defaultMessage: '账户密码登录',
}),
},
{
key: 'mobile',
label: intl.formatMessage({
id: 'pages.login.phoneLogin.tab',
defaultMessage: '手机号登录',
}),
},
]}
/>
{code !== 200 && loginType === 'account' && (
<LoginMessage
content={intl.formatMessage({
id: 'pages.login.accountLogin.errorMessage',
defaultMessage: '账户或密码错误(admin/admin123)',
})}
/>
)}
{type === 'account' && (
<>
<ProFormText
name="username"
initialValue="admin"
fieldProps={{
size: 'large',
prefix: <UserOutlined />,
}}
placeholder={intl.formatMessage({
id: 'pages.login.username.placeholder',
defaultMessage: '用户名: admin',
})}
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.login.username.required"
defaultMessage="请输入用户名!"
/>
),
},
]}
/>
<ProFormText.Password
name="password"
initialValue="admin123"
fieldProps={{
size: 'large',
prefix: <LockOutlined />,
}}
placeholder={intl.formatMessage({
id: 'pages.login.password.placeholder',
defaultMessage: '密码: admin123',
})}
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.login.password.required"
defaultMessage="请输入密码!"
/>
),
},
]}
/>
<Row>
<Col flex={3}>
<ProFormText
style={{
float: 'right',
}}
name="code"
placeholder={intl.formatMessage({
id: 'pages.login.captcha.placeholder',
defaultMessage: '请输入验证',
})}
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.searchTable.updateForm.ruleName.nameRules"
defaultMessage="请输入验证啊"
/>
),
},
]}
/>
</Col>
<Col flex={2}>
<Image
src={captchaCode}
alt="验证码"
style={{
display: 'inline-block',
verticalAlign: 'top',
cursor: 'pointer',
paddingLeft: '10px',
width: '100px',
}}
preview={false}
onClick={() => getCaptchaCode()}
/>
</Col>
</Row>
</>
)}
{code !== 200 && loginType === 'mobile' && <LoginMessage content="验证码错误" />}
{type === 'mobile' && (
<>
<ProFormText
fieldProps={{
size: 'large',
prefix: <MobileOutlined />,
}}
name="mobile"
placeholder={intl.formatMessage({
id: 'pages.login.phoneNumber.placeholder',
defaultMessage: '手机号',
})}
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.login.phoneNumber.required"
defaultMessage="请输入手机号!"
/>
),
},
{
pattern: /^1\d{10}$/,
message: (
<FormattedMessage
id="pages.login.phoneNumber.invalid"
defaultMessage="手机号格式错误!"
/>
),
},
]}
/>
<ProFormCaptcha
fieldProps={{
size: 'large',
prefix: <LockOutlined />,
}}
captchaProps={{
size: 'large',
}}
placeholder={intl.formatMessage({
id: 'pages.login.captcha.placeholder',
defaultMessage: '请输入验证码',
})}
captchaTextRender={(timing, count) => {
if (timing) {
return `${count} ${intl.formatMessage({
id: 'pages.getCaptchaSecondText',
defaultMessage: '获取验证码',
})}`;
}
return intl.formatMessage({
id: 'pages.login.phoneLogin.getVerificationCode',
defaultMessage: '获取验证码',
});
}}
name="captcha"
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.login.captcha.required"
defaultMessage="请输入验证码!"
/>
),
},
]}
onGetCaptcha={async (phone) => {
const result = await getFakeCaptcha({
phone,
});
if (!result) {
return;
}
message.success('获取验证码成功验证码为1234');
}}
/>
</>
)}
<div
style={{
marginBottom: 24,
}}
>
<ProFormCheckbox noStyle name="autoLogin">
<FormattedMessage id="pages.login.rememberMe" defaultMessage="自动登录" />
</ProFormCheckbox>
<a
style={{
float: 'right',
}}
>
<FormattedMessage id="pages.login.forgotPassword" defaultMessage="忘记密码" />
</a>
</div>
</LoginForm>
</div>
<Footer />
</div>
);
};
export default Login;

View File

@@ -0,0 +1,20 @@
import { PageContainer } from '@ant-design/pro-components';
import { Card } from 'antd';
import React from 'react';
/**
*
* @author whiteshader@163.com
*
* */
const Settings: React.FC = () => {
return (
<PageContainer>
<Card title="Developing" />
</PageContainer>
);
};
export default Settings;