新增用户重置密码和用户修改密码
This commit is contained in:
lq1405 2025-03-22 20:50:24 +08:00
parent 9a2bc86427
commit df0da4c73d
9 changed files with 378 additions and 32 deletions

View File

@ -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',
}
]
},

View File

@ -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": {

View File

@ -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,

View File

@ -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>
);
};

View File

@ -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">

View 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;

View 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;

View File

@ -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>
);
};

View File

@ -21,6 +21,12 @@ declare namespace AccessType {
//#region 软件配置项操作权限
/** 是不是可以操作配置型 */
canOptions: boolean;
/** 是不是显示LaiTool配置项的菜单 */
canLaiToolOptions: boolean;
/** 是不是显示系统配置项的菜单 */
canSystemOptions : boolean;
//#endregion
//#region 机器权限