From 505562892ffd2ae476a284c6231e83c46c539354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AD=99=E6=99=AF=E5=AD=A6?= <5412262+sun_jing_xue@user.noreply.gitee.com> Date: Fri, 18 Jul 2025 13:39:45 +0800 Subject: [PATCH] =?UTF-8?q?=E5=BF=98=E8=AE=B0=E5=AF=86=E7=A0=81=E4=B8=8E?= =?UTF-8?q?=E9=83=A8=E9=97=A8=E6=94=B9=E4=B8=BA=E6=87=92=E5=8A=A0=E8=BD=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app.ts | 2 +- .../AccessDepartmentSelect/index.tsx | 90 +++++--- .../AccessDepartmentSelect/services.ts | 9 +- src/pages/forgot/forget.less | 195 +++++++++++++++++- src/pages/forgot/forgot.tsx | 187 ++++++++++++++++- src/pages/login/login.tsx | 16 +- .../components/ViewModal.tsx | 16 +- src/servers/api/login.ts | 26 +++ 8 files changed, 480 insertions(+), 61 deletions(-) diff --git a/src/app.ts b/src/app.ts index 305fc41..471fc84 100644 --- a/src/app.ts +++ b/src/app.ts @@ -2,7 +2,7 @@ import { history } from 'umi'; export function onRouteChange({ location }: any) { const token = sessionStorage.getItem('token'); - const whiteList = ['/login', '/register/supplier', '/register/expert', '/403', '/404']; + const whiteList = ['/login', '/forgot', '/403', '/404']; if (!token && !whiteList.includes(location.pathname)) { history.replace('/login'); } diff --git a/src/components/AccessDepartmentSelect/index.tsx b/src/components/AccessDepartmentSelect/index.tsx index b3cfbdc..e8bbc15 100644 --- a/src/components/AccessDepartmentSelect/index.tsx +++ b/src/components/AccessDepartmentSelect/index.tsx @@ -1,17 +1,23 @@ -import React, { useState, useEffect } from 'react'; -import { TreeSelect } from 'antd'; +import React, { useEffect, useState } from 'react'; +import { TreeSelect, Spin } from 'antd'; +import { treeData } from './services'; // 你的接口 export interface AccessDepartmentSelectProps { value?: string | number; onChange?: (value: string | number) => void; placeholder?: string; disabled?: boolean; + style?: React.CSSProperties; } -interface orgData { - orgName: string; +// 树节点类型 +interface OrgNode { orgId: string; - children?: orgData[]; + orgName: string; + children?: OrgNode[]; + isLeaf?: boolean; + key?: string; + title?: string; } const AccessDepartmentSelect: React.FC = ({ @@ -19,36 +25,70 @@ const AccessDepartmentSelect: React.FC = ({ onChange, placeholder = '请选择准入部门', disabled = false, + style = { width: 200 }, }) => { - const [userOrgAll, setUserOrgAll] = useState([]); + const [tree, setTree] = useState([]); + const [loading, setLoading] = useState(false); - useEffect(() => { - const userOrgAllStr = sessionStorage.getItem('userOrgAll'); - const userOrgAll = userOrgAllStr ? JSON.parse(userOrgAllStr) : []; - setUserOrgAll(userOrgAll); - }, []); - - // TreeSelect 要的数据结构 - function buildTree(data: orgData[]): any[] { + // 转换接口为 TreeSelect 结构 + function formatTree(data: OrgNode[]): any[] { return data.map(item => ({ + ...item, title: item.orgName, value: item.orgId, key: item.orgId, - children: item.children && item.children.length > 0 ? buildTree(item.children) : undefined, + // isLeaf: item.isLeaf || false, + // children: item.children && item.children.length > 0 ? formatTree(item.children) : undefined, })); } + // 懒加载节点 + const onLoadData = async (treeNode: any) => { + if (treeNode.children && treeNode.children.length > 0) return; + setLoading(true); + const { code, data } = await treeData({ upOrgId: treeNode.value }); + if (code === 200 && Array.isArray(data)) { + setTree(origin => updateNode(origin, treeNode.value, formatTree(data))); + } + setLoading(false); + }; + + // 递归插入children + function updateNode(list: OrgNode[], key: string, children: OrgNode[]): OrgNode[] { + return list.map(node => { + if (node.orgId === key) return { ...node, children }; + if (node.children) return { ...node, children: updateNode(node.children, key, children) }; + return node; + }); + } + + // 初始化 + useEffect(() => { + setLoading(true); + treeData({}).then(res => { + if (res.code === 200 && Array.isArray(res.data)) { + setTree(formatTree(res.data)); + } + setLoading(false); + }); + }, []); + return ( - + + + ); }; diff --git a/src/components/AccessDepartmentSelect/services.ts b/src/components/AccessDepartmentSelect/services.ts index b6dcf3d..c7c7c07 100644 --- a/src/components/AccessDepartmentSelect/services.ts +++ b/src/components/AccessDepartmentSelect/services.ts @@ -4,8 +4,13 @@ import request from '@/utils/request'; * 部门 */ export const queryUserOrgAll = () => request.get(`/org/queryUserOrgAll`); - - +/** + * 部门列表 + */ +interface treeInterface { + upOrgId?: string +} +export const treeData = (params: treeInterface) => request.get('/org/list', { params }); diff --git a/src/pages/forgot/forget.less b/src/pages/forgot/forget.less index 5b5b5d2..59e3140 100644 --- a/src/pages/forgot/forget.less +++ b/src/pages/forgot/forget.less @@ -1,3 +1,194 @@ -.forgotContainer{ - background: red; +@import '~@/baseStyle.less'; + +// 登录页面整体布局 +.login-page { + display: flex; + align-items: center; + justify-content: flex-end; + min-height: 100vh; + padding-right: 10%; + background: #f0f2f5; + background-image: url('~@/assets/img/loginBg.jpg'); + background-position: center; + background-size: cover; +} + +// 登录容器 +.login-container { + position: relative; + width: 400px; + padding: 30px; + background: #fff; + border-radius: 8px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); +} + +// 标题样式 +.login-title { + margin-bottom: 20px; + color: @main-color; + font-weight: bold; + font-size: 24px; + text-align: center; +} + +// 返回首页链接 +.back-home { + position: absolute; + top: 10px; + left: 10px; + + a { + display: flex; + align-items: center; + color: @main-color; + font-size: 14px; + + &:hover { + color: lighten(@main-color, 10%); + } + + .anticon { + margin-right: 4px; + } + } +} + +// Tab 容器 +.login-tab-container { + margin-bottom: 30px; +} + +// Tab 样式 +.login-tabs { + border-radius: 15px; + .ant-tabs-nav { + margin: 0; + } + + .ant-tabs-nav::before { + display: none; + } + + .ant-tabs-tab { + flex: 1; + height: 44px; + margin: 0; + padding: 0; + font-weight: bold; + font-size: 16px; + line-height: 44px; + text-align: center; + background-color: #f5f5f5; + border: none; + transition: none; + + .ant-tabs-tab-btn { + width: 100%; + height: 100%; + color: #333; + } + } + + .ant-tabs-nav-list { + display: flex; + width: 100%; + overflow: hidden; + } + + .ant-tabs-tab-active { + background-color: @main-color; + border-radius: 15px; + + .ant-tabs-tab-btn { + color: #fff !important; + font-weight: bold; + } + } + + .ant-tabs-ink-bar { + display: none; + } +} + +// 登录表单 +.login-form { + .ant-form-item { + margin-bottom: 20px; + } + + .ant-input-affix-wrapper { + border-radius: 10px; + + &:focus, + &-focused, + &:hover { + border-color: @main-color; + } + } +} + +// 登录选项区域(记住密码和忘记密码) +.login-options { + display: flex; + align-items: center; + justify-content: space-between; + + .ant-checkbox-checked .ant-checkbox-inner { + background-color: @main-color; + border-color: @main-color; + } + + .login-form-forgot { + color: @main-color; + + &:hover { + color: lighten(@main-color, 10%); + } + } +} + +// 登录按钮 +.login-form-button { + width: 100%; + height: 40px; + margin-top: 10px; + background-color: @main-color; + border-color: @main-color; + border-radius: 4px; + + &:hover, + &:focus { + background-color: lighten(@main-color, 10%); + border-color: lighten(@main-color, 10%); + } +} + +// 注册链接 +.register-link { + margin-top: 15px; + text-align: center; + + a { + margin-left: 5px; + color: @main-color; + + &:hover { + color: lighten(@main-color, 10%); + } + } +} + +// 响应式布局 +@media (max-width: 768px) { + .login-page { + justify-content: center; + padding-right: 0; + } + + .login-container { + width: 90%; + max-width: 400px; + padding: 20px; + } } diff --git a/src/pages/forgot/forgot.tsx b/src/pages/forgot/forgot.tsx index 2fa56f3..34a7968 100644 --- a/src/pages/forgot/forgot.tsx +++ b/src/pages/forgot/forgot.tsx @@ -1,12 +1,177 @@ -import React from 'react'; -import styles from './forget.less'; -import { useLocation } from 'umi'; -const Forgot: React.FC = () => { - const location = useLocation(); - const search = location.search; - const params = new URLSearchParams(search); - console.log(params); - const type = params.get('type'); - return
忘记密码
; +import React, { useState, useEffect } from 'react'; +import { Form, Input, Button, message } from 'antd'; +import { UserOutlined, LockOutlined, EyeInvisibleOutlined, EyeTwoTone, HomeOutlined } from '@ant-design/icons'; +import { history, useIntl } from 'umi'; +import './forget.less'; +import { sendCode, reset } from '@/servers/api/login'; +import { encryptWithRsa } from '@/utils/encryptWithRsa'; + +const LoginPage: React.FC = () => { + const [form] = Form.useForm(); + const [loading, setLoading] = useState(false); + const [sending, setSending] = useState(false); // 是否在发送验证码 + const [countdown, setCountdown] = useState(0); // 倒计时 + const intl = useIntl(); + + // 倒计时效果 + useEffect(() => { + let timer: any; + if (countdown > 0) { + timer = setTimeout(() => setCountdown(countdown - 1), 1000); + } + return () => clearTimeout(timer); + }, [countdown]); + + // 获取验证码 + const handleSendCode = async () => { + const account = form.getFieldValue('account'); + if (!account) { + message.warning('请先输入账号/手机号'); + return; + } + setSending(true); + try { + const res = await sendCode({ account }); + if (res.code === 200) { + message.success('验证码已发送'); + setCountdown(60); // 60秒倒计时 + } else { + message.error(res.message || '验证码发送失败'); + } + } finally { + setSending(false); + } + }; + + // 表单提交 + const onFinish = async (values: any) => { + setLoading(true); + try { + const params = { + ...values, + password: encryptWithRsa(values.password, false), + encryptValue: encryptWithRsa(values.code) // 验证码 + }; + const loginRes = await reset(params); + if (loginRes.code === 200) { + message.success('修改成功'); + history.push('/login'); + } else { + message.error(loginRes.message || '修改失败'); + } + } finally { + setLoading(false); + } + }; + + return ( +
+
+ +
{intl.formatMessage({ id: 'login.title', defaultMessage: '忘记密码' })}
+
+ + } + placeholder="请输入账号/手机号" + size="large" + allowClear + /> + + + + } + placeholder="请输入新密码" + iconRender={visible => (visible ? : )} + size="large" + allowClear + /> + + + ({ + validator(_, value) { + if (!value || getFieldValue('password') === value) { + return Promise.resolve(); + } + return Promise.reject(new Error('两次输入的密码不一致!')); + }, + }), + ]} + > + } + placeholder="请确认新密码" + iconRender={visible => (visible ? : )} + size="large" + allowClear + /> + + + + + + + + + + + + + + + + +
+
+
+ ); }; -export default Forgot; + +export default LoginPage; diff --git a/src/pages/login/login.tsx b/src/pages/login/login.tsx index f1be97e..de0bb87 100644 --- a/src/pages/login/login.tsx +++ b/src/pages/login/login.tsx @@ -72,12 +72,12 @@ const LoginPage: React.FC = () => { //存入招标代理用户id sessionStorage.setItem('userId', loginRes.data.user.userId); //部门 - queryUserOrgAll().then((res) => { - const { code, data } = res; - if (code == 200) { - sessionStorage.setItem('userOrgAll', JSON.stringify(data) ); - } - }) + // queryUserOrgAll().then((res) => { + // const { code, data } = res; + // if (code == 200) { + // sessionStorage.setItem('userOrgAll', JSON.stringify(data) ); + // } + // }) } sessionStorage.setItem('currentUser', JSON.stringify(loginRes.data)); @@ -88,8 +88,8 @@ const LoginPage: React.FC = () => { }) console.log(roleIdList, 'roleIdList'); - const menuList = await findMenuList({ roleIdList }); - sessionStorage.setItem('menuList', JSON.stringify(menuList.data)); + // const menuList = await findMenuList({ roleIdList }); + // sessionStorage.setItem('menuList', JSON.stringify(menuList.data)); // } }) diff --git a/src/pages/supplier/category/SupplierEntryReview/components/ViewModal.tsx b/src/pages/supplier/category/SupplierEntryReview/components/ViewModal.tsx index 1c50de4..24bfa41 100644 --- a/src/pages/supplier/category/SupplierEntryReview/components/ViewModal.tsx +++ b/src/pages/supplier/category/SupplierEntryReview/components/ViewModal.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import { Modal, Button, Descriptions, Tooltip, Table } from 'antd'; -import { detail, treeList } from "../services"; +import { detail } from "../services"; import type { DataNode } from 'antd/es/tree'; import type { ColumnsType } from 'antd/es/table'; import { useSupplierDetailModal } from '@/components/SupplierDetailModalContext/SupplierDetailModalContext'; @@ -50,9 +50,9 @@ const ViewModal: React.FC<{ // 已勾选的供应商 id // 品类库基本信息 const [categoryInfo, setCategoryInfo] = useState({}); - const [treeData, setTreeData] = useState([]); - const [expandedKeys, setExpandedKeys] = useState([]); - const [autoExpandParent, setAutoExpandParent] = useState(true); + // const [treeData, setTreeData] = useState([]); + // const [expandedKeys, setExpandedKeys] = useState([]); + // const [autoExpandParent, setAutoExpandParent] = useState(true); const supplierDetailModal = useSupplierDetailModal(); // 拉取基本信息 @@ -61,14 +61,6 @@ const ViewModal: React.FC<{ const res = await detail({ id: storeId }); if (res.code === 200 && res.data) setCategoryInfo(res.data); - // const treeRes = await treeList({ categoryLibraryId: storeId }); - // if (treeRes.code === 200 && treeRes.data) { - // // categoryIds - // const formatted = formatData(res.data) - // // setTreeData(); - // } - - }; const formatData = (list: any[]): LockNode[] => { return list.map(item => ({ diff --git a/src/servers/api/login.ts b/src/servers/api/login.ts index 90a63e7..bb31809 100644 --- a/src/servers/api/login.ts +++ b/src/servers/api/login.ts @@ -70,4 +70,30 @@ export async function queryUserOrgAll() { return request('/org/queryUserOrgAll', { method: 'GET' }); +} +/** + * 发送验证码 + */ +interface sendCodeData { + account: string; +} +export async function sendCode(data: sendCodeData) { + return request('/v1/login/forgotPassword/sendCode', { + method: 'POST', + data + }); +} +/** + * 校验验证码并设置新密码 + */ +interface resetData { + account: string; + identifying: string; + password: string; +} +export async function reset(data: resetData) { + return request('/v1/login/forgotPassword/reset', { + method: 'POST', + data + }); } \ No newline at end of file