V 1.0.5
新增用户重置密码和用户修改密码
This commit is contained in:
parent
9a2bc86427
commit
df0da4c73d
@ -73,13 +73,13 @@ export default [
|
||||
name: 'laitoolOptions',
|
||||
path: '/options/laitoolOptions',
|
||||
component: './Options/LaitoolOptions/LaitoolOptions/index',
|
||||
access: 'canOptions',
|
||||
access: 'canLaiToolOptions',
|
||||
},
|
||||
{
|
||||
name: 'systemOptions',
|
||||
path: '/options/systemOptions',
|
||||
component: './Options/SystemOptions/index',
|
||||
access: 'canOptions',
|
||||
access: 'canSystemOptions',
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "lms",
|
||||
"version": "1.0.4",
|
||||
"version": "1.0.5",
|
||||
"private": true,
|
||||
"description": "An out-of-box UI solution for enterprise applications",
|
||||
"scripts": {
|
||||
|
||||
@ -16,6 +16,8 @@ export default function access(initialState: { currentUser?: API.CurrentUser } |
|
||||
isAdminOrSuperAdmin: false,
|
||||
|
||||
canOptions: false,
|
||||
canLaiToolOptions : false,
|
||||
canSystemOptions : false,
|
||||
|
||||
canApplySoftwareControl: false,
|
||||
canSofrwareControlManagement: false,
|
||||
@ -59,7 +61,7 @@ export default function access(initialState: { currentUser?: API.CurrentUser } |
|
||||
if (currentUser?.roleNames?.includes("Admin")) {
|
||||
access = {
|
||||
...access,
|
||||
canPrompt: false,
|
||||
canPrompt: true,
|
||||
|
||||
canUserManagement: true,
|
||||
canEditUser: true,
|
||||
@ -67,7 +69,9 @@ export default function access(initialState: { currentUser?: API.CurrentUser } |
|
||||
isAdmin: true,
|
||||
isAdminOrSuperAdmin: true,
|
||||
|
||||
canOptions: false,
|
||||
canOptions: true,
|
||||
canLaiToolOptions : true,
|
||||
canSystemOptions : false,
|
||||
|
||||
canMachineManagement: true,
|
||||
canEditMachine: true,
|
||||
@ -93,6 +97,8 @@ export default function access(initialState: { currentUser?: API.CurrentUser } |
|
||||
isAdminOrSuperAdmin: true,
|
||||
|
||||
canOptions: true,
|
||||
canLaiToolOptions : true,
|
||||
canSystemOptions : true,
|
||||
|
||||
canMachineManagement: true,
|
||||
canEditMachine: true,
|
||||
|
||||
@ -16,13 +16,13 @@ import {
|
||||
ProFormText,
|
||||
} from '@ant-design/pro-components';
|
||||
import { FormattedMessage, history, SelectLang, useIntl, useModel, Helmet } from '@umijs/max';
|
||||
import { Alert, message, Tabs } from 'antd';
|
||||
import { Alert, message, Tabs, Form, Modal } from 'antd';
|
||||
import Settings from '../../../../config/defaultSettings';
|
||||
import React, { useState } from 'react';
|
||||
import { flushSync } from 'react-dom';
|
||||
import { createStyles } from 'antd-style';
|
||||
import { TokenStorage } from '@/services/define/tokenStorage';
|
||||
|
||||
import ResetPassword from '../ResetPassword';
|
||||
|
||||
const useStyles = createStyles(({ token }) => {
|
||||
return {
|
||||
@ -60,18 +60,6 @@ const useStyles = createStyles(({ token }) => {
|
||||
};
|
||||
});
|
||||
|
||||
const ActionIcons = () => {
|
||||
const { styles } = useStyles();
|
||||
|
||||
return (
|
||||
<>
|
||||
<AlipayCircleOutlined key="AlipayCircleOutlined" className={styles.action} />
|
||||
<TaobaoCircleOutlined key="TaobaoCircleOutlined" className={styles.action} />
|
||||
<WeiboCircleOutlined key="WeiboCircleOutlined" className={styles.action} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const Lang = () => {
|
||||
const { styles } = useStyles();
|
||||
|
||||
@ -104,6 +92,10 @@ const Login: React.FC = () => {
|
||||
const { styles } = useStyles();
|
||||
const intl = useIntl();
|
||||
let tokenStorage = new TokenStorage();
|
||||
const [messageApi, contextHolder] = message.useMessage();
|
||||
|
||||
// 重置密码相关状态
|
||||
const [resetModalVisible, setResetModalVisible] = useState(false);
|
||||
|
||||
const fetchUserInfo = async () => {
|
||||
let tokenObj = await tokenStorage.getTokenAndDecode();
|
||||
@ -137,6 +129,7 @@ const Login: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{contextHolder}
|
||||
<Helmet>
|
||||
<title>
|
||||
{intl.formatMessage({
|
||||
@ -164,14 +157,6 @@ const Login: React.FC = () => {
|
||||
initialValues={{
|
||||
autoLogin: true,
|
||||
}}
|
||||
// actions={[
|
||||
// <FormattedMessage
|
||||
// key="loginWith"
|
||||
// id="pages.login.loginWith"
|
||||
// defaultMessage="其他登录方式"
|
||||
// />,
|
||||
// <ActionIcons key="icons" />,
|
||||
// ]}
|
||||
onFinish={async (values) => {
|
||||
await handleSubmit(values as API.LoginParams);
|
||||
}}
|
||||
@ -363,7 +348,7 @@ const Login: React.FC = () => {
|
||||
|
||||
<a
|
||||
onClick={() => {
|
||||
alert("请联系管理员重置密码")
|
||||
setResetModalVisible(true);
|
||||
}}
|
||||
|
||||
>
|
||||
@ -375,6 +360,24 @@ const Login: React.FC = () => {
|
||||
</LoginForm>
|
||||
</div>
|
||||
<Footer />
|
||||
|
||||
{/* 重置密码弹窗 */}
|
||||
<Modal
|
||||
title="重置密码"
|
||||
open={resetModalVisible}
|
||||
onCancel={() => setResetModalVisible(false)}
|
||||
footer={null}
|
||||
destroyOnClose
|
||||
width={500}
|
||||
>
|
||||
<ResetPassword
|
||||
onCancel={() => setResetModalVisible(false)}
|
||||
onSuccess={() => {
|
||||
messageApi.success('重置密码成功,新密码将发送到您的邮箱,请尽快在个人中心修改密码');
|
||||
setResetModalVisible(false);
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -127,7 +127,10 @@ const Register: React.FC = () => {
|
||||
|
||||
<Form.Item
|
||||
name="verificationCode"
|
||||
rules={[{ required: true, message: '请输入邮箱验证码!' }]}
|
||||
rules={[
|
||||
{ required: true, message: '请输入邮箱验证码!' },
|
||||
{ pattern: /^[a-z0-9]{6}$/, message: '验证码必须是6位数字或小写字母' }
|
||||
]}
|
||||
>
|
||||
<Row gutter={8}>
|
||||
<Col flex="auto">
|
||||
|
||||
172
src/pages/User/ResetPassword/index.tsx
Normal file
172
src/pages/User/ResetPassword/index.tsx
Normal file
@ -0,0 +1,172 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Form, Input, Button, message, Row, Col } from 'antd';
|
||||
import { request } from '@umijs/max';
|
||||
|
||||
interface ResetPasswordProps {
|
||||
onCancel: () => void;
|
||||
onSuccess?: () => void;
|
||||
}
|
||||
|
||||
const ResetPassword: React.FC<ResetPasswordProps> = ({ onCancel, onSuccess }) => {
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [countdown, setCountdown] = useState(0);
|
||||
const [messageApi, contextHolder] = message.useMessage();
|
||||
|
||||
// 发送重置密码验证码
|
||||
const sendResetCode = async () => {
|
||||
try {
|
||||
const emailValue = form.getFieldValue('email');
|
||||
if (!emailValue) {
|
||||
messageApi.warning('请输入邮箱');
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证邮箱格式
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(emailValue)) {
|
||||
messageApi.warning('请输入有效的邮箱格式');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
// 发送验证码请求
|
||||
const res = await request<ApiResponse.SuccessItem<string>>('/lms/User/SendResetPasswordCode', {
|
||||
method: 'POST',
|
||||
data: { email: emailValue }
|
||||
});
|
||||
|
||||
if (res.code !== 1) {
|
||||
throw new Error(res.message);
|
||||
}
|
||||
|
||||
// 设置倒计时
|
||||
setCountdown(60);
|
||||
const timer = setInterval(() => {
|
||||
setCountdown((prevCountdown) => {
|
||||
if (prevCountdown <= 1) {
|
||||
clearInterval(timer);
|
||||
return 0;
|
||||
}
|
||||
return prevCountdown - 1;
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
messageApi.success('验证码已发送,请查收邮箱');
|
||||
} catch (error: any) {
|
||||
messageApi.error(error.message || '发送验证码失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 提交重置密码请求
|
||||
const handleResetPassword = async () => {
|
||||
try {
|
||||
debugger;
|
||||
const values = form.getFieldsValue();
|
||||
|
||||
setLoading(true);
|
||||
// 发送重置密码请求
|
||||
const res = await request<ApiResponse.SuccessItem<string>>(`/lms/User/ResetPassword/${values.email}/${values.verificationCode}`, {
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
if (res.code !== 1) {
|
||||
throw new Error(res.message);
|
||||
}
|
||||
|
||||
messageApi.success('重置密码成功,新密码将发送到您的邮箱,请尽快在个人中心修改密码');
|
||||
onSuccess?.();
|
||||
onCancel();
|
||||
form.resetFields();
|
||||
} catch (error: any) {
|
||||
messageApi.error(error.message || '重置密码失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ padding: '10px 0' }}>
|
||||
{contextHolder}
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
name="resetPassword"
|
||||
onFinish={handleResetPassword}
|
||||
size="large" // 设置整个表单的尺寸为大号
|
||||
>
|
||||
<Form.Item
|
||||
name="email"
|
||||
label={<span style={{ fontSize: '16px' }}>邮箱</span>}
|
||||
rules={[
|
||||
{ required: true, message: '请输入邮箱' },
|
||||
{ type: 'email', message: '请输入有效的邮箱格式' }
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
placeholder="请输入您的邮箱"
|
||||
style={{ height: '45px', fontSize: '16px' }}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="verificationCode"
|
||||
label={<span style={{ fontSize: '16px' }}>验证码</span>}
|
||||
rules={[
|
||||
{ required: true, message: '请输入验证码' },
|
||||
{ pattern: /^[a-z0-9]{6}$/, message: '验证码必须是6位数字或小写字母' }
|
||||
]}
|
||||
>
|
||||
<Row gutter={12}>
|
||||
<Col flex="auto">
|
||||
<Input
|
||||
placeholder="请输入验证码"
|
||||
style={{ height: '45px', fontSize: '16px' }}
|
||||
/>
|
||||
</Col>
|
||||
<Col>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={sendResetCode}
|
||||
disabled={countdown > 0}
|
||||
loading={loading && countdown === 0}
|
||||
style={{
|
||||
width: '120px',
|
||||
height: '45px',
|
||||
fontSize: '16px',
|
||||
background: countdown > 0 ? '#f0f0f0' : '#1890ff',
|
||||
borderColor: countdown > 0 ? '#d9d9d9' : '#1890ff',
|
||||
color: countdown > 0 ? '#595959' : '#fff',
|
||||
fontWeight: 500,
|
||||
borderRadius: '4px',
|
||||
}}
|
||||
>
|
||||
{countdown > 0 ? `${countdown}秒` : '获取验证码'}
|
||||
</Button>
|
||||
</Col>
|
||||
</Row>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item style={{ marginTop: '30px' }}>
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType='submit'
|
||||
loading={loading}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '45px',
|
||||
fontSize: '16px',
|
||||
fontWeight: 'bold'
|
||||
}}
|
||||
>
|
||||
重置密码
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResetPassword;
|
||||
142
src/pages/User/UserCenter/ModifyPassword.tsx
Normal file
142
src/pages/User/UserCenter/ModifyPassword.tsx
Normal file
@ -0,0 +1,142 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Form, Input, Button, message, Card, Typography } from 'antd';
|
||||
import { LockOutlined } from '@ant-design/icons';
|
||||
import { request, useModel } from '@umijs/max';
|
||||
import { isEmpty } from 'lodash';
|
||||
|
||||
const { Title } = Typography;
|
||||
|
||||
const ResetPassword: React.FC = () => {
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [messageApi, contextHolder] = message.useMessage();
|
||||
const { initialState, setInitialState } = useModel('@@initialState');
|
||||
|
||||
// 提交重置密码请求
|
||||
const handleResetPassword = async () => {
|
||||
try {
|
||||
debugger;
|
||||
const values = form.getFieldsValue();
|
||||
if (initialState?.currentUser?.id == null) {
|
||||
messageApi.error('用户信息不存在,请重新登录');
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查两次密码是否一致
|
||||
if (values.newPassword !== values.confirmPassword) {
|
||||
messageApi.error('两次输入的密码不一致');
|
||||
return;
|
||||
}
|
||||
if (isEmpty(values.newPassword)) {
|
||||
messageApi.error('请输入新密码');
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
// 发送重置密码请求
|
||||
const res = await request<ApiResponse.SuccessItem<string>>(`/lms/User/ResetPassword/${initialState?.currentUser?.id}`, {
|
||||
method: 'POST',
|
||||
data: {
|
||||
"newPassword": values.newPassword,
|
||||
}
|
||||
});
|
||||
|
||||
if (res.code !== 1) {
|
||||
throw new Error(res.message);
|
||||
}
|
||||
messageApi.success('密码重置成功');
|
||||
form.resetFields();
|
||||
} catch (error: any) {
|
||||
messageApi.error(error.message || '密码重置失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Card
|
||||
bordered={false}
|
||||
style={{
|
||||
borderRadius: '8px',
|
||||
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.05)',
|
||||
margin: "20px"
|
||||
}}
|
||||
>
|
||||
{contextHolder}
|
||||
<div style={{ textAlign: 'center', marginBottom: '20px' }}>
|
||||
<LockOutlined style={{ fontSize: '28px', color: '#1890ff', marginBottom: '12px' }} />
|
||||
<Title level={4} style={{ margin: 0 }}>设置新密码</Title>
|
||||
</div>
|
||||
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
name="resetPassword"
|
||||
size="large"
|
||||
onFinish={handleResetPassword}
|
||||
>
|
||||
<Form.Item
|
||||
name="newPassword"
|
||||
rules={[
|
||||
{ required: true, message: '请输入新密码' },
|
||||
{
|
||||
pattern: /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?.&.])[A-Za-z\d@$!%*?.&]{8,}$/,
|
||||
message: '密码必须至少8位,包含大小写字母、数字和特殊字符'
|
||||
}
|
||||
]}
|
||||
>
|
||||
<Input.Password
|
||||
prefix={<LockOutlined style={{ color: '#d9d9d9' }} />}
|
||||
placeholder="请输入新密码"
|
||||
style={{
|
||||
height: '45px',
|
||||
borderRadius: '4px',
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="confirmPassword"
|
||||
dependencies={['newPassword']}
|
||||
rules={[
|
||||
{ required: true, message: '请再次输入新密码' },
|
||||
({ getFieldValue }) => ({
|
||||
validator(_, value) {
|
||||
if (!value || getFieldValue('newPassword') === value) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(new Error('两次输入的密码不一致'));
|
||||
},
|
||||
})
|
||||
]}
|
||||
>
|
||||
<Input.Password
|
||||
prefix={<LockOutlined style={{ color: '#d9d9d9' }} />}
|
||||
placeholder="请再次输入新密码"
|
||||
style={{
|
||||
height: '45px',
|
||||
borderRadius: '4px',
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item style={{ marginTop: '24px', marginBottom: '0' }}>
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
loading={loading}
|
||||
style={{
|
||||
width: '100%',
|
||||
height: '45px',
|
||||
borderRadius: '4px',
|
||||
}}
|
||||
>
|
||||
确认修改
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default ResetPassword;
|
||||
@ -1,29 +1,42 @@
|
||||
import React from 'react';
|
||||
import { Button, Card, Divider, Dropdown, message } from 'antd';
|
||||
import { Button, Card, Divider, Dropdown, message, Modal } from 'antd';
|
||||
import Icon, { LockOutlined, MailOutlined, PhoneOutlined, UserOutlined } from '@ant-design/icons';
|
||||
import renderTitle from '../UserRenderList';
|
||||
import { useModel } from '@umijs/max';
|
||||
import { isEmpty } from 'lodash';
|
||||
import ModifyPassword from '../ModifyPassword';
|
||||
|
||||
const UserCenterUserInfo: React.FC = () => {
|
||||
|
||||
const [messageApi, messageHolder] = message.useMessage();
|
||||
const [modalApi, modalHolder] = Modal.useModal();
|
||||
const { initialState } = useModel('@@initialState');
|
||||
|
||||
async function ModifyPasswordFunc() {
|
||||
modalApi.info({
|
||||
title: null,
|
||||
icon: null,
|
||||
closable: true,
|
||||
closeIcon: true,
|
||||
footer: null,
|
||||
content: <ModifyPassword />,
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<Card hoverable title={renderTitle({
|
||||
title: '个人信息',
|
||||
subTitle: '修改密码、邮箱、电话号码等',
|
||||
icon: <UserOutlined style={{ fontSize: 24 }} />,
|
||||
height: 100
|
||||
})} style={{ width: "100%" }}>
|
||||
})} style={{ width: "100%"}}>
|
||||
{renderTitle({
|
||||
title: '密码修改',
|
||||
subTitle: '**********',
|
||||
icon: <LockOutlined style={{ fontSize: 24 }} />,
|
||||
style: { marginLeft: 10 },
|
||||
height: 60,
|
||||
button: <Button color="primary" variant="filled" onClick={() => { messageApi.info("该功能目前不能用") }}>
|
||||
button: <Button color="primary" variant="filled" onClick={ModifyPasswordFunc}>
|
||||
修改
|
||||
</Button>
|
||||
})}
|
||||
@ -50,6 +63,7 @@ const UserCenterUserInfo: React.FC = () => {
|
||||
</Button>
|
||||
})}
|
||||
{messageHolder}
|
||||
{modalHolder}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
6
src/services/typing/access.d.ts
vendored
6
src/services/typing/access.d.ts
vendored
@ -21,6 +21,12 @@ declare namespace AccessType {
|
||||
//#region 软件配置项操作权限
|
||||
/** 是不是可以操作配置型 */
|
||||
canOptions: boolean;
|
||||
|
||||
/** 是不是显示LaiTool配置项的菜单 */
|
||||
canLaiToolOptions: boolean;
|
||||
|
||||
/** 是不是显示系统配置项的菜单 */
|
||||
canSystemOptions : boolean;
|
||||
//#endregion
|
||||
|
||||
//#region 机器权限
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user