diff --git a/config/routes.ts b/config/routes.ts index c3564fa..0188c33 100644 --- a/config/routes.ts +++ b/config/routes.ts @@ -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', } ] }, diff --git a/package.json b/package.json index 1246552..caf6c48 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/src/access.ts b/src/access.ts index 366d77e..fda1dcc 100644 --- a/src/access.ts +++ b/src/access.ts @@ -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, diff --git a/src/pages/User/Login/index.tsx b/src/pages/User/Login/index.tsx index 00fabae..b857560 100644 --- a/src/pages/User/Login/index.tsx +++ b/src/pages/User/Login/index.tsx @@ -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 ( - <> - - - - - ); -}; - 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 (
+ {contextHolder} {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> ); }; diff --git a/src/pages/User/Register/index.tsx b/src/pages/User/Register/index.tsx index 941362c..6c1770f 100644 --- a/src/pages/User/Register/index.tsx +++ b/src/pages/User/Register/index.tsx @@ -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"> diff --git a/src/pages/User/ResetPassword/index.tsx b/src/pages/User/ResetPassword/index.tsx new file mode 100644 index 0000000..8258939 --- /dev/null +++ b/src/pages/User/ResetPassword/index.tsx @@ -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; diff --git a/src/pages/User/UserCenter/ModifyPassword.tsx b/src/pages/User/UserCenter/ModifyPassword.tsx new file mode 100644 index 0000000..5be219c --- /dev/null +++ b/src/pages/User/UserCenter/ModifyPassword.tsx @@ -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 }}>设置新密码 +
+ +
+ + } + placeholder="请输入新密码" + style={{ + height: '45px', + borderRadius: '4px', + }} + /> + + + ({ + validator(_, value) { + if (!value || getFieldValue('newPassword') === value) { + return Promise.resolve(); + } + return Promise.reject(new Error('两次输入的密码不一致')); + }, + }) + ]} + > + } + placeholder="请再次输入新密码" + style={{ + height: '45px', + borderRadius: '4px', + }} + /> + + + + + +
+ + ); +}; + +export default ResetPassword; diff --git a/src/pages/User/UserCenter/UserCenterUserInfo/index.tsx b/src/pages/User/UserCenter/UserCenterUserInfo/index.tsx index 0b881b9..6470c14 100644 --- a/src/pages/User/UserCenter/UserCenterUserInfo/index.tsx +++ b/src/pages/User/UserCenter/UserCenterUserInfo/index.tsx @@ -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: , + }) + } + return ( , height: 100 - })} style={{ width: "100%" }}> + })} style={{ width: "100%"}}> {renderTitle({ title: '密码修改', subTitle: '**********', icon: , style: { marginLeft: 10 }, height: 60, - button: })} @@ -50,6 +63,7 @@ const UserCenterUserInfo: React.FC = () => { })} {messageHolder} + {modalHolder} ); }; diff --git a/src/services/typing/access.d.ts b/src/services/typing/access.d.ts index deb1513..7798192 100644 --- a/src/services/typing/access.d.ts +++ b/src/services/typing/access.d.ts @@ -21,6 +21,12 @@ declare namespace AccessType { //#region 软件配置项操作权限 /** 是不是可以操作配置型 */ canOptions: boolean; + + /** 是不是显示LaiTool配置项的菜单 */ + canLaiToolOptions: boolean; + + /** 是不是显示系统配置项的菜单 */ + canSystemOptions : boolean; //#endregion //#region 机器权限