From 7114466b2673ddbc18d559e8f39b33f9ff7cd30d Mon Sep 17 00:00:00 2001 From: linxd <544554903@qq.com> Date: Thu, 3 Jul 2025 14:40:22 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8A=A8=E6=80=81=E6=9B=B4=E6=96=B0=E9=9D=A2?= =?UTF-8?q?=E5=8C=85=E5=B1=91=E5=AF=BC=E8=88=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 123 +++++ src/layouts/BasicLayout.tsx | 39 +- src/layouts/Index.tsx | 64 --- src/layouts/SiderMenu.tsx | 127 ----- src/models/breadcrumb.ts | 132 +---- src/models/index.ts | 3 + .../supplierAnnualTaskManageAdd.tsx | 17 +- .../supplierAnnualTemplateManageAdd.tsx | 522 +++++++++++------- .../supplierTaskManageAdd.tsx | 32 +- .../supplierTemplateManageAdd.tsx | 38 +- 10 files changed, 586 insertions(+), 511 deletions(-) create mode 100644 README.md delete mode 100644 src/layouts/Index.tsx delete mode 100644 src/layouts/SiderMenu.tsx diff --git a/README.md b/README.md new file mode 100644 index 0000000..f25d71a --- /dev/null +++ b/README.md @@ -0,0 +1,123 @@ +# 动态面包屑导航实现说明 + +## 功能介绍 + +本项目实现了动态修改页面面包屑名称的功能,特别适用于新增和修改页面共用一个组件的场景。通过 dva 状态管理,可以在具体页面中动态设置面包屑导航的最后一级显示文本。 + +## 实现原理 + +1. 通过 `breadcrumb` 模型管理面包屑状态 +2. 使用 `connect` 将 `breadcrumb` 状态连接到布局组件 +3. 在页面组件中根据不同条件(如是否有 id 参数)来动态设置面包屑名称 + +## 代码结构 + +- `src/models/breadcrumb.ts` - 面包屑状态模型 +- `src/layouts/BasicLayout.tsx` - 支持动态面包屑的布局组件 + +## 使用示例 + +### 实际项目中的使用方式 (supplierAnnualTemplateManageAdd.tsx) + +```tsx +import React, { useEffect } from 'react'; +import { useIntl, connect } from 'umi'; +import type { Dispatch } from 'umi'; +import type { BreadcrumbState } from '@/models/breadcrumb'; + +// 组件 props 接口定义 +interface PageProps extends ConnectProps { + breadcrumb: BreadcrumbState; // dva model状态 + dispatch: Dispatch; // dva dispatch方法 +} + +const YourPage: React.FC = ({ dispatch }) => { + const intl = useIntl(); + const location = useLocation(); + + // 设置面包屑名称 + useEffect(() => { + if (location.state?.editData?.id && dispatch) { + // 编辑模式,设置编辑相关的面包屑 + dispatch({ + type: 'breadcrumb/updateBreadcrumbName', + payload: intl.formatMessage({ id: "yourNamespace.add.edit" }), + }); + } else { + // 新增模式,设置新增相关的面包屑 + dispatch({ + type: 'breadcrumb/updateBreadcrumbName', + payload: intl.formatMessage({ id: "yourNamespace.add.title" }), + }); + } + + // 组件卸载时重置面包屑 + return () => { + dispatch({ + type: 'breadcrumb/resetBreadcrumb', + }); + }; + }, [dispatch, intl, location]); + + // 页面其他内容... + return ( +
+ {/* 页面内容... */} +
+ ); +}; + +// 将dva model中的状态映射到组件props +export default connect(({ breadcrumb }: { breadcrumb: BreadcrumbState }) => ({ + breadcrumb, +}))(YourPage); +``` + +### 关键实现点 + +1. 在页面组件中通过 connect 连接到 dva 状态: + +```tsx +// 将dva model中的状态映射到组件props +export default connect(({ breadcrumb }: { breadcrumb: BreadcrumbState }) => ({ + breadcrumb, +}))(YourPage); +``` + +2. 在 useEffect 中动态设置面包屑名称: + +```tsx +useEffect(() => { + // 根据条件设置不同的面包屑名称 + if (location.state?.editData?.id) { + dispatch({ + type: 'breadcrumb/updateBreadcrumbName', + payload: intl.formatMessage({ id: "yourNamespace.add.edit" }), + }); + } else { + dispatch({ + type: 'breadcrumb/updateBreadcrumbName', + payload: intl.formatMessage({ id: "yourNamespace.add.title" }), + }); + } + + // 组件卸载时重置面包屑 + return () => { + dispatch({ + type: 'breadcrumb/resetBreadcrumb', + }); + }; +}, [dispatch, intl, location]); +``` + +## 注意事项 + +1. **payload 必须是字符串**:传递给 `updateBreadcrumbName` action 的 payload 必须是字符串类型,不能是对象或 React 组件 + - 正确:`payload: intl.formatMessage({ id: "menu.edit" })` + - 错误:`payload: { breadcrumbName: }` + +2. **重置面包屑**:确保在组件卸载时调用 `resetBreadcrumb` 以重置面包屑状态 + +3. **国际化处理**:使用 `intl.formatMessage()` 方法处理国际化文本,不要在 payload 中直接使用 FormattedMessage 组件 + +4. **依赖项管理**:确保 useEffect 的依赖项包含 `dispatch`, `intl` 和用于条件判断的变量(如 `location`) diff --git a/src/layouts/BasicLayout.tsx b/src/layouts/BasicLayout.tsx index c6e2339..ca1b9b2 100644 --- a/src/layouts/BasicLayout.tsx +++ b/src/layouts/BasicLayout.tsx @@ -2,11 +2,13 @@ import React from 'react'; import ProLayout, { PageContainer } from '@ant-design/pro-layout'; import { Link, useLocation, useIntl, useHistory } from 'umi'; +import { connect } from 'dva'; import defaultSettings from '../../config/defaultSettings'; import routes from '../../config/router.config'; // 引入你的自定义路由结构 import { ConfigProvider, Breadcrumb } from 'antd'; import HeaderComponent from './Header'; import IconFont from '@/components/IconFont/IconFont'; +import { BreadcrumbState } from '@/models/breadcrumb'; const MenuRender = (item: any, isSubMenu: boolean) => { @@ -32,22 +34,29 @@ const MenuRender = (item: any, isSubMenu: boolean) => { ); }; -const BreadcrumbRender = (breadcrumb: any, intl: any, history: any) => { - const breadcrumbRoutes = breadcrumb?.routes; +const BreadcrumbRender = (routeBreadcrumb: any, intl: any, history: any, dynamicBreadcrumbName: string | null) => { + const breadcrumbRoutes = routeBreadcrumb?.routes; return ( { history.push('/'); }} - style={{ cursor: 'pointer' }} > - {intl.formatMessage({ id: 'menu.首页' })} + + {intl.formatMessage({ id: 'menu.首页' })} + - {breadcrumbRoutes?.map((item: any) => { + {breadcrumbRoutes?.map((item: any, index: number) => { + // 判断是否是最后一个面包屑项且存在动态名称 + const isLastItem = index === (breadcrumbRoutes.length - 1); + const displayName = (isLastItem && dynamicBreadcrumbName) + ? dynamicBreadcrumbName + : intl.formatMessage({ id: `menu.${item.breadcrumbName}` || '' }); + return ( - {intl.formatMessage({ id: `menu.${item.breadcrumbName}` || '' })} + {displayName} ); })} @@ -55,7 +64,13 @@ const BreadcrumbRender = (breadcrumb: any, intl: any, history: any) => { ); }; -const BasicLayout: React.FC = (props) => { +interface BasicLayoutProps { + children: React.ReactNode; + breadcrumb: BreadcrumbState; +} + +const BasicLayout: React.FC = (props) => { + const { children, breadcrumb } = props; const location = useLocation(); const intl = useIntl(); const history = useHistory(); @@ -81,14 +96,18 @@ const BasicLayout: React.FC = (props) => { ghost={true} header={{ title: false, - breadcrumbRender: ({ breadcrumb }) => BreadcrumbRender(breadcrumb, intl, history), + breadcrumbRender: ({ breadcrumb: routeBreadcrumb }) => + BreadcrumbRender(routeBreadcrumb, intl, history, breadcrumb.breadcrumbName), }} > - {props.children} + {children} ); }; -export default BasicLayout; +export default connect(({ breadcrumb }: { breadcrumb: BreadcrumbState }) => ({ + breadcrumb +}))(BasicLayout); + diff --git a/src/layouts/Index.tsx b/src/layouts/Index.tsx deleted file mode 100644 index 09a4b13..0000000 --- a/src/layouts/Index.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React, { useEffect } from 'react'; -// import Header from './Header'; -import { Layout, Breadcrumb } from 'antd'; - import { useLocation, useIntl, Link, connect } from 'umi'; - -import type { ConnectProps, Dispatch } from 'umi'; -const { Header, Sider, Content } = Layout; -//导入logo图片 -import HeaderComponent from './Header'; -import SiderMenu from './SiderMenu'; -import './layout.less'; -import type { BreadcrumbModelState } from '@/models/breadcrumb'; - -interface LayoutIndexProps extends ConnectProps { - breadcrumb: BreadcrumbModelState; - dispatch: Dispatch; -} - -const LayoutIndex: React.FC = (props) => { - const { children, breadcrumb, dispatch } = props; - const location = useLocation(); - const intl = useIntl(); - // 当路由变化时更新面包屑 - useEffect(() => { - console.log(location) - dispatch({ - type: 'breadcrumb/updateBreadcrumbs', - payload: { pathname: location.pathname, intl }, - }); - }, [location.pathname, intl, dispatch]); - - return ( - <> - -
- -
- - - - - - - {breadcrumb.breadcrumbs.map((breadcrumbItem, index) => ( - - {index < breadcrumb.breadcrumbs.length - 1 ? ( - {breadcrumbItem.breadcrumbName} - ) : ( - breadcrumbItem.breadcrumbName - )} - - ))} - - {children} - - -
- - ); -}; - -export default connect(({ breadcrumb }: { breadcrumb: BreadcrumbModelState }) => ({ - breadcrumb, -}))(LayoutIndex); diff --git a/src/layouts/SiderMenu.tsx b/src/layouts/SiderMenu.tsx deleted file mode 100644 index 2320312..0000000 --- a/src/layouts/SiderMenu.tsx +++ /dev/null @@ -1,127 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Menu, Tooltip } from 'antd'; -import { useIntl, Link, useHistory } from 'umi'; -import IconFont from '@/components/IconFont/IconFont'; -import routerConfig from '../../config/router.config'; - -// 路由接口定义 -interface IRouteItem { - path: string; - component?: string; - name?: string; - meta?: { - title?: string; - hide?: boolean; - icon?: string; - }; - routes?: IRouteItem[]; - children?: IRouteItem[]; - redirect?: string; -} - -interface IMenuItem { - label: string; - key: string; - path: string; - icon: string; - children?: IMenuItem[]; -} - -// 获取路由配置转换为菜单项 -const generateMenuItems = (): IMenuItem[] => { - // 找到主布局下的路由 - const mainLayoutRoute = routerConfig.find((route: IRouteItem) => route.path === '/'); - if (!mainLayoutRoute || !mainLayoutRoute.routes) return []; - - // 递归处理路由,生成菜单项 - const processRoutes = (routes: IRouteItem[], parentPath: string = ''): IMenuItem[] => { - return routes - .filter((route) => !route.redirect && route.name && !route.meta?.hide) - .map((route) => { - // 构建完整路径 - const routePath = route.path.startsWith('/') - ? route.path - : `${parentPath}/${route.path}`.replace(/\/+/g, '/'); - - // 创建菜单项 - const menuItem: IMenuItem = { - label: route.meta?.title ? `menu.${route.meta.title}` : `menu.${route.name || ''}`, - key: route.path || '', - path: routePath, - icon: route.meta?.icon || 'icon-liebiaomoshi', - }; - - // 如果有子路由,递归处理 - if (route.routes && route.routes.length > 0) { - const children = processRoutes(route.routes, routePath); - if (children.length > 0) { - menuItem.children = children; - } - } - - // 如果有children,递归处理 - if (route.children && route.children.length > 0) { - const children = processRoutes(route.children, routePath); - if (children.length > 0) { - menuItem.children = children; - } - } - - return menuItem; - }); - }; - - return processRoutes(mainLayoutRoute.routes); -}; - -const items: IMenuItem[] = generateMenuItems(); - -const SiderMenu: React.FC = (props: any) => { - //当前激活菜单 - const [current, setCurrent] = useState('index'); - const intl = useIntl(); - const history = useHistory(); - useEffect(() => { - // 获取当前激活菜单 - const path = history.location.pathname; - setCurrent(path); - // if (path.split('/').length > 1) { - // setCurrent(path.split('/')[path.split('/').length - 1]); - // return; - // } - }, [history.location.pathname]); - - // 递归渲染菜单项 - const renderMenuItems = (menuItems: IMenuItem[]) => { - return menuItems.map((item: IMenuItem) => - item.children && item.children.length > 0 ? ( - - <>{intl.formatMessage({ id: item.label })} - - } - icon={} - > - {renderMenuItems(item.children)} - - ) : ( - }> - - {intl.formatMessage({ id: item.label })} - - - ), - ); - }; - - return ( -
- - {renderMenuItems(items)} - -
- ); -}; -export default SiderMenu; diff --git a/src/models/breadcrumb.ts b/src/models/breadcrumb.ts index 88e5601..de2d704 100644 --- a/src/models/breadcrumb.ts +++ b/src/models/breadcrumb.ts @@ -1,136 +1,60 @@ -import { Effect, Reducer } from 'umi'; -export interface BreadcrumbItem { - path: string; - breadcrumbName: string; -} +type Effect = (action: { payload: any }, effects: { call: any; put: any; select: any }) => Generator; +type Reducer = (state: S, action: { payload: any }) => S; -export interface BreadcrumbModelState { - breadcrumbs: BreadcrumbItem[]; +export interface BreadcrumbState { + breadcrumbName: string | null; } export interface BreadcrumbModelType { namespace: 'breadcrumb'; - state: BreadcrumbModelState; + state: BreadcrumbState; effects: { - updateBreadcrumbs: Effect; + updateBreadcrumbName: Effect; + resetBreadcrumb: Effect; }; reducers: { - saveBreadcrumbs: Reducer; + setBreadcrumbName: Reducer; + resetState: Reducer; }; } -/** - * 生成面包屑数据 - * @param pathname 当前路径 - * @param intl 国际化对象 - */ -const generateBreadcrumbs = (pathname: string, intl: any): BreadcrumbItem[] => { - // 如果是首页,直接返回首页 - if (pathname === '/' || pathname === '/index') { - return [ - { - path: '/', - breadcrumbName: intl.formatMessage({ id: 'menu.首页' }, { defaultMessage: '首页' }), - }, - ]; - } - - // 构建面包屑数据 - const breadcrumbs = [ - { - path: '/', - breadcrumbName: intl.formatMessage({ id: 'menu.首页' }, { defaultMessage: '首页' }), - }, - ]; - - // 按照斜杠分割路径 - const pathSegments = pathname.split('/').filter(segment => segment); - let currentPath = ''; - - // 构建每一级的路径和对应的面包屑名称 - for (let i = 0; i < pathSegments.length; i++) { - const segment = pathSegments[i]; - currentPath += `/${segment}`; - - // 尝试使用多语言配置 - // 先尝试完整路径的多语言配置 - let menuKey = `menu.${pathSegments.slice(0, i + 1).join('/')}`; - let menuName = intl.formatMessage({ id: menuKey }, { defaultMessage: '' }); - - // 如果完整路径没有对应的多语言,则尝试使用当前段的多语言 - if (!menuName) { - menuKey = `menu.${segment}`; - menuName = intl.formatMessage({ id: menuKey }, { defaultMessage: '' }); - } - - // 如果仍然没有找到多语言配置,尝试使用特定页面的多语言配置 - if (!menuName) { - // 处理特殊情况,如数据统计下的子页面 - if (pathSegments[0] === 'dataStatistics') { - switch (segment) { - case 'supplierEvaluateStatistics': - menuName = intl.formatMessage({ id: 'menu.供应商评价情况统计' }, { defaultMessage: '供应商评价情况统计' }); - break; - case 'supplierAnnualStatistics': - menuName = intl.formatMessage({ id: 'menu.供应商年审情况统计' }, { defaultMessage: '供应商年审情况统计' }); - break; - case 'supplierQualificationWarningStatistics': - menuName = intl.formatMessage({ id: 'menu.供应商资质预警统计' }, { defaultMessage: '供应商资质预警统计' }); - break; - case 'supplierExitStatistics': - menuName = intl.formatMessage({ id: 'menu.供应商清退情况统计' }, { defaultMessage: '供应商清退情况统计' }); - break; - default: - menuName = segment; - } - } else if (pathSegments[0] === 'supplierEvaluate') { - // 处理供应商评价模块 - menuName = intl.formatMessage({ id: `menu.${segment}` }, { defaultMessage: segment }); - } else if (pathSegments[0] === 'supplierAnnual') { - // 处理供应商年审模块 - menuName = intl.formatMessage({ id: `menu.${segment}` }, { defaultMessage: segment }); - } else { - menuName = segment; - } - } - - breadcrumbs.push({ - path: currentPath, - breadcrumbName: menuName || segment, - }); - } - - return breadcrumbs; -}; - const BreadcrumbModel: BreadcrumbModelType = { namespace: 'breadcrumb', state: { - breadcrumbs: [], + breadcrumbName: null, }, effects: { - *updateBreadcrumbs({ payload }, { put }) { - const { pathname, intl } = payload; - console.log(pathname) - const breadcrumbs = generateBreadcrumbs(pathname, intl); + *updateBreadcrumbName({ payload }, { put }) { yield put({ - type: 'saveBreadcrumbs', - payload: breadcrumbs, + type: 'setBreadcrumbName', + payload, + }); + }, + + *resetBreadcrumb(_, { put }) { + yield put({ + type: 'resetState', }); }, }, reducers: { - saveBreadcrumbs(state, { payload }) { + setBreadcrumbName(state: BreadcrumbState, { payload }: { payload: string }) { return { ...state, - breadcrumbs: payload, + breadcrumbName: payload, + }; + }, + + resetState() { + return { + breadcrumbName: null, }; }, }, }; -export default BreadcrumbModel; +export default BreadcrumbModel; \ No newline at end of file diff --git a/src/models/index.ts b/src/models/index.ts index 01d4583..d60d29e 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -1,5 +1,8 @@ import globalModal from './globalModal'; +import breadcrumb from './breadcrumb'; + export default { globalModal, + breadcrumb, // ... 其他 models }; diff --git a/src/pages/supplierAnnualManage/supplierAnnualTaskManage/supplierAnnualTaskManageAdd.tsx b/src/pages/supplierAnnualManage/supplierAnnualTaskManage/supplierAnnualTaskManageAdd.tsx index d7ef2a5..a20514f 100644 --- a/src/pages/supplierAnnualManage/supplierAnnualTaskManage/supplierAnnualTaskManageAdd.tsx +++ b/src/pages/supplierAnnualManage/supplierAnnualTaskManage/supplierAnnualTaskManageAdd.tsx @@ -8,6 +8,7 @@ import SupplierSelectStep from './components/SupplierSelectStep'; import EvaluatorSelectStep from './components/EvaluatorSelectStep'; import styles from './supplierAnnualTaskManageAdd.less'; import type { SupplierTaskModelState } from '@/models/supplierAnnualTaskManage'; +import type { BreadcrumbState } from '@/models/breadcrumb'; const { Step } = Steps; @@ -38,7 +39,7 @@ interface PageProps extends ConnectProps { * 供应商任务管理添加/编辑组件 * 使用步骤式表单引导用户完成添加或编辑任务的流程 */ -const SupplierTaskManageAdd: React.FC = ({ supplierAnnualTaskManage, dispatch }) => { +const SupplierTaskManageAdd: React.FC = ({ supplierAnnualTaskManage, dispatch, breadcrumb }) => { const intl = useIntl(); // 获取dva model中的状态 const { currentStep, loading, detailLoading } = supplierAnnualTaskManage; @@ -85,14 +86,23 @@ const SupplierTaskManageAdd: React.FC = ({ supplierAnnualTaskManage, * 组件卸载时重置状态 */ useEffect(() => { + if (isEditMode && dispatch) { + dispatch({ + type: 'breadcrumb/updateBreadcrumbName', + payload: intl.formatMessage({ id: 'supplierAnnualTaskManage.edit.title' }), + }); + } return () => { if (dispatch) { dispatch({ type: 'supplierAnnualTaskManage/resetState', }); + dispatch({ + type: 'breadcrumb/resetBreadcrumb', + }); } }; - }, [dispatch]); + }, [dispatch, isEditMode]); // 步骤配置,定义每个步骤的标题、描述和内容组件 const steps = [ @@ -274,7 +284,8 @@ const SupplierTaskManageAdd: React.FC = ({ supplierAnnualTaskManage, // 将dva model中的状态映射到组件props export default connect( - ({ supplierAnnualTaskManage }: { supplierAnnualTaskManage: SupplierTaskModelState }) => ({ + ({ supplierAnnualTaskManage, breadcrumb }: { supplierAnnualTaskManage: SupplierTaskModelState, breadcrumb: BreadcrumbState }) => ({ supplierAnnualTaskManage, + breadcrumb, }), )(SupplierTaskManageAdd); diff --git a/src/pages/supplierAnnualManage/supplierAnnualTemplateManage/supplierAnnualTemplateManageAdd.tsx b/src/pages/supplierAnnualManage/supplierAnnualTemplateManage/supplierAnnualTemplateManageAdd.tsx index 73e9e45..1d425e0 100644 --- a/src/pages/supplierAnnualManage/supplierAnnualTemplateManage/supplierAnnualTemplateManageAdd.tsx +++ b/src/pages/supplierAnnualManage/supplierAnnualTemplateManage/supplierAnnualTemplateManageAdd.tsx @@ -17,23 +17,17 @@ import { Popconfirm, Modal, } from 'antd'; -import { history, useLocation, useIntl } from 'umi'; -import { - ArrowLeftOutlined, - SaveOutlined, - PlusOutlined, - DeleteOutlined, -} from '@ant-design/icons'; +import { history, useLocation, useIntl, connect, FormattedMessage } from 'umi'; +import type { ConnectProps, Dispatch } from 'umi'; +import { ArrowLeftOutlined, SaveOutlined, PlusOutlined, DeleteOutlined } from '@ant-design/icons'; import CategorySelector from '@/components/CategorySelector'; -import { - AnnualTemplateStatus, - AnnualTemplateStatusText, -} from '@/dicts/supplierAnnualDict'; +import type { BreadcrumbState } from '@/models/breadcrumb'; +import { AnnualTemplateStatus, AnnualTemplateStatusText } from '@/dicts/supplierAnnualDict'; import { addAnnualTemplate, updateAnnualTemplate, getAnnualTemplateDetail, - getAllAnnualTemplates + getAllAnnualTemplates, } from '@/servers/api/supplierAnnual'; import styles from './supplierAnnualTemplateManage.less'; @@ -53,33 +47,30 @@ const CategoryLimitationType = { UNIVERSAL: '0', // 不限 LIMITED: '1', // 限制 }; - -// 品类限制类型文本 -const CategoryLimitationTypeText = { - [CategoryLimitationType.UNIVERSAL]: '不限', - [CategoryLimitationType.LIMITED]: '限制', -}; - // 是否星号项常量 const StarOptions = { YES: '1', NO: '0', }; -// 是否星号项文本 -const StarOptionsText = { - [StarOptions.YES]: '是', - [StarOptions.NO]: '否', -}; - -const SupplierAnnualTemplateManageAdd: React.FC = () => { +interface PageProps extends ConnectProps { + breadcrumb: BreadcrumbState; // dva model状态 + dispatch: Dispatch; // dva dispatch方法 +} +const SupplierAnnualTemplateManageAdd: React.FC = ({ breadcrumb, dispatch }) => { const intl = useIntl(); + const [form] = Form.useForm(); const [loading, setLoading] = useState(false); const [isEdit, setIsEdit] = useState(false); - const [templateDetail, setTemplateDetail] = useState(null); - const [templateList, setTemplateList] = useState([]); - const [indicatorList, setIndicatorList] = useState([]); + const [templateDetail, setTemplateDetail] = + useState(null); + const [templateList, setTemplateList] = useState( + [], + ); + const [indicatorList, setIndicatorList] = useState( + [], + ); // 获取路由传递的数据 const location = useLocation(); @@ -90,9 +81,22 @@ const SupplierAnnualTemplateManageAdd: React.FC = () => { setLoading(true); const res = await getAllAnnualTemplates(); if (res.success && res.data) { - setTemplateList(res.data); + // 如果是修改,需要过滤掉自己 + if (location.state?.editData) { + setTemplateList( + res.data.filter( + (template: supplierAnnualTemplateManage.TemplateRecord) => + template.id !== location.state.editData?.id, + ), + ); + } else { + setTemplateList(res.data); + } } else { - message.error(res.message || intl.formatMessage({ id: 'supplierAnnualTemplateManage.list.getListFailed' })); + message.error( + res.message || + intl.formatMessage({ id: 'supplierAnnualTemplateManage.list.getListFailed' }), + ); } setLoading(false); } catch (error) { @@ -124,7 +128,10 @@ const SupplierAnnualTemplateManageAdd: React.FC = () => { setIndicatorList(res.data.indicatorList); } } else { - message.error(res.message || intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.getDetailFailed' })); + message.error( + res.message || + intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.getDetailFailed' }), + ); } setLoading(false); } catch (error) { @@ -133,7 +140,21 @@ const SupplierAnnualTemplateManageAdd: React.FC = () => { setLoading(false); } }; + useEffect(() => { + if (location.state?.editData?.id && dispatch) { + dispatch({ + type: 'breadcrumb/updateBreadcrumbName', + payload: intl.formatMessage({ id: "supplierAnnualTemplateManage.add.edit" }), + }); + } + // 组件卸载时重置面包屑 + return () => { + dispatch({ + type: 'breadcrumb/resetBreadcrumb', + }); + }; + }, [dispatch, intl, location]); // 初始化编辑数据 useEffect(() => { // 获取所有模板列表 @@ -176,20 +197,27 @@ const SupplierAnnualTemplateManageAdd: React.FC = () => { } if (res && res.success) { - message.success(isEdit - ? intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.submitSuccess' }) - : intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.saveSuccess' })); + message.success( + isEdit + ? intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.submitSuccess' }) + : intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.saveSuccess' }), + ); history.goBack(); } else { - message.error(res?.message || (isEdit - ? intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.submitFailed' }) - : intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.saveFailed' }))); + message.error( + res?.message || + (isEdit + ? intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.submitFailed' }) + : intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.saveFailed' })), + ); } } catch (error) { console.error('提交失败:', error); - message.error(isEdit - ? intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.submitFailed' }) - : intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.saveFailed' })); + message.error( + isEdit + ? intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.submitFailed' }) + : intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.saveFailed' }), + ); } finally { setLoading(false); } @@ -199,17 +227,21 @@ const SupplierAnnualTemplateManageAdd: React.FC = () => { const handleSubmit = async (values: any) => { // 检查指标列表 if (!indicatorList || indicatorList.length === 0) { - message.error(intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.atLeastOneIndicator' })); + message.error( + intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.atLeastOneIndicator' }), + ); return; } // 检查指标名称不能为空 - const emptyNameIndex = indicatorList.findIndex(item => !item.itemName); + const emptyNameIndex = indicatorList.findIndex((item) => !item.itemName); if (emptyNameIndex !== -1) { - message.error(intl.formatMessage( - { id: 'supplierAnnualTemplateManage.add.indicatorNameRequired' }, - { index: emptyNameIndex + 1 } - )); + message.error( + intl.formatMessage( + { id: 'supplierAnnualTemplateManage.add.indicatorNameRequired' }, + { index: emptyNameIndex + 1 }, + ), + ); return; } @@ -235,7 +267,7 @@ const SupplierAnnualTemplateManageAdd: React.FC = () => { cancelText: intl.formatMessage({ id: 'supplierAnnualTemplateManage.common.cancel' }), onOk: async () => { await submitFormData(submitData); - } + }, }); }; @@ -255,20 +287,27 @@ const SupplierAnnualTemplateManageAdd: React.FC = () => { // 复制指标列表 if (res.data.indicatorList && res.data.indicatorList.length > 0) { - const copiedList = JSON.parse(JSON.stringify(res.data.indicatorList)).map((item: any) => { - // 删除id,防止ID冲突 - delete item.id; - return item; - }); + const copiedList = JSON.parse(JSON.stringify(res.data.indicatorList)).map( + (item: any) => { + // 删除id,防止ID冲突 + delete item.id; + return item; + }, + ); setIndicatorList(copiedList); } } else { - message.error(res.message || intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.getDetailFailed' })); + message.error( + res.message || + intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.getDetailFailed' }), + ); } setLoading(false); } catch (error) { console.error('获取模板详情失败:', error); - message.error(intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.getDetailFailed' })); + message.error( + intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.getDetailFailed' }), + ); setLoading(false); } } @@ -320,7 +359,9 @@ const SupplierAnnualTemplateManageAdd: React.FC = () => { handleIndicatorChange(index, 'itemName', e.target.value)} - placeholder={`${intl.formatMessage({ id: 'supplierAnnualTemplateManage.common.pleaseInput' })}${intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.checkItem' })}`} + placeholder={`${intl.formatMessage({ + id: 'supplierAnnualTemplateManage.common.pleaseInput', + })}${intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.checkItem' })}`} /> ), }, @@ -335,8 +376,12 @@ const SupplierAnnualTemplateManageAdd: React.FC = () => { onChange={(value) => handleIndicatorChange(index, 'isStar', value)} className={styles.starSelector} > - - + + ), }, @@ -355,12 +400,19 @@ const SupplierAnnualTemplateManageAdd: React.FC = () => { /> )} handleDeleteIndicator(index)} okText={intl.formatMessage({ id: 'supplierAnnualTemplateManage.common.confirm' })} cancelText={intl.formatMessage({ id: 'supplierAnnualTemplateManage.common.cancel' })} > - +
+ + {isEdit + ? intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.edit' }) + : intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.title' })} + + +
+ +
+ + + + + + + + + + + + + {intl.formatMessage({ + id: 'supplierAnnualTemplateManage.add.categoryLimitationUniversal', + })} + + + {intl.formatMessage({ + id: 'supplierAnnualTemplateManage.add.categoryLimitationLimited', + })} + + + + + + + prevValues.categoryLimitation !== currentValues.categoryLimitation + } + > + {({ getFieldValue }) => { + const categoryLimitation = getFieldValue('categoryLimitation'); + return categoryLimitation === CategoryLimitationType.LIMITED ? ( + + + + ) : null; + }} + + + + + + + + + + + + + + + {AnnualTemplateStatusText[AnnualTemplateStatus.DRAFT]} + + + {AnnualTemplateStatusText[AnnualTemplateStatus.ENABLED]} + + + {AnnualTemplateStatusText[AnnualTemplateStatus.DISABLED]} + + + + + + + + + + + + + + +
+ + + +
- - - - - - - - - - - - - - - {intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.categoryLimitationUniversal' })} - - - {intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.categoryLimitationLimited' })} - - - - - - - prevValues.categoryLimitation !== currentValues.categoryLimitation - } - > - {({ getFieldValue }) => { - const categoryLimitation = getFieldValue('categoryLimitation'); - return categoryLimitation === CategoryLimitationType.LIMITED ? ( - - - - ) : null; - }} - - - - - - - - - - - - - - {AnnualTemplateStatusText[AnnualTemplateStatus.DRAFT]} - {AnnualTemplateStatusText[AnnualTemplateStatus.ENABLED]} - {AnnualTemplateStatusText[AnnualTemplateStatus.DISABLED]} - - - - - - - - - -
- - - -
- - - - -
- + ); }; -export default SupplierAnnualTemplateManageAdd; +// export default SupplierAnnualTemplateManageAdd; +// 将dva model中的状态映射到组件props +export default connect(({ breadcrumb }: { breadcrumb: BreadcrumbState }) => ({ + breadcrumb, +}))(SupplierAnnualTemplateManageAdd); diff --git a/src/pages/supplierEvaluateManage/supplierTaskManage/supplierTaskManageAdd.tsx b/src/pages/supplierEvaluateManage/supplierTaskManage/supplierTaskManageAdd.tsx index 9ec23cd..c381c8e 100644 --- a/src/pages/supplierEvaluateManage/supplierTaskManage/supplierTaskManageAdd.tsx +++ b/src/pages/supplierEvaluateManage/supplierTaskManage/supplierTaskManageAdd.tsx @@ -10,6 +10,7 @@ import DivisionStep from './components/DivisionStep'; import styles from './supplierTaskManageAdd.less'; import { TaskNotifyLowerUnits } from '@/dicts/supplierTaskDict'; import type { SupplierTaskModelState } from '@/models/supplierTaskManage'; +import type { BreadcrumbState } from '@/models/breadcrumb'; const { Step } = Steps; @@ -40,7 +41,11 @@ interface PageProps extends ConnectProps { * 供应商任务管理添加/编辑组件 * 使用步骤式表单引导用户完成添加或编辑任务的流程 */ -const SupplierTaskManageAdd: React.FC = ({ supplierTaskManage, dispatch }) => { +const SupplierTaskManageAdd: React.FC = ({ + supplierTaskManage, + dispatch, + breadcrumb, +}) => { const intl = useIntl(); // 获取dva model中的状态 const { currentStep, loading, detailLoading } = supplierTaskManage; @@ -70,6 +75,11 @@ const SupplierTaskManageAdd: React.FC = ({ supplierTaskManage, dispat type: 'supplierTaskManage/saveMode', payload: 'edit', }); + // 更新面包屑 + dispatch({ + type: 'breadcrumb/updateBreadcrumbName', + payload: intl.formatMessage({ id: 'supplierTaskManage.title.edit' }), + }); // 编辑模式,获取任务详情 dispatch({ type: 'supplierTaskManage/fetchTaskDetail', @@ -106,6 +116,9 @@ const SupplierTaskManageAdd: React.FC = ({ supplierTaskManage, dispat dispatch({ type: 'supplierTaskManage/resetState', }); + dispatch({ + type: 'breadcrumb/resetBreadcrumb', + }); } }; }, [dispatch]); @@ -124,7 +137,9 @@ const SupplierTaskManageAdd: React.FC = ({ supplierTaskManage, dispat }, { title: intl.formatMessage({ id: 'supplierTaskManage.step.selectEvaluator.title' }), - description: intl.formatMessage({ id: 'supplierTaskManage.step.selectEvaluator.description' }), + description: intl.formatMessage({ + id: 'supplierTaskManage.step.selectEvaluator.description', + }), content: , }, { @@ -177,7 +192,9 @@ const SupplierTaskManageAdd: React.FC = ({ supplierTaskManage, dispat if (typeof errorInfo === 'string') { message.error(errorInfo); } else if (errorInfo && errorInfo.errorFields) { - message.error(intl.formatMessage({ id: 'supplierTaskManage.message.formValidationFailed' })); + message.error( + intl.formatMessage({ id: 'supplierTaskManage.message.formValidationFailed' }), + ); } } }; @@ -357,7 +374,14 @@ const SupplierTaskManageAdd: React.FC = ({ supplierTaskManage, dispat // 将dva model中的状态映射到组件props export default connect( - ({ supplierTaskManage }: { supplierTaskManage: SupplierTaskModelState }) => ({ + ({ supplierTaskManage, + breadcrumb, + }: { + supplierTaskManage: SupplierTaskModelState; + breadcrumb: BreadcrumbState; + }) => ({ + supplierTaskManage, + breadcrumb, }), )(SupplierTaskManageAdd); diff --git a/src/pages/supplierEvaluateManage/supplierTemplateManage/supplierTemplateManageAdd.tsx b/src/pages/supplierEvaluateManage/supplierTemplateManage/supplierTemplateManageAdd.tsx index 405842b..575d573 100644 --- a/src/pages/supplierEvaluateManage/supplierTemplateManage/supplierTemplateManageAdd.tsx +++ b/src/pages/supplierEvaluateManage/supplierTemplateManage/supplierTemplateManageAdd.tsx @@ -16,7 +16,9 @@ import { Spin, Modal, } from 'antd'; -import { history, useLocation, useIntl } from 'umi'; +import { history, useLocation, useIntl, connect } from 'umi'; +import type { Dispatch,ConnectProps } from 'umi'; +import type { BreadcrumbState } from '@/models/breadcrumb'; import { ArrowLeftOutlined, SaveOutlined, ExclamationCircleOutlined } from '@ant-design/icons'; import EvaluateTemplateTable from '@/components/EvaluateTemplateTable'; import CategorySelector from '@/components/CategorySelector'; @@ -50,6 +52,11 @@ interface FormValues { [key: string]: any; } +interface PageProps extends ConnectProps { + breadcrumb: BreadcrumbState; // dva model状态 + dispatch: Dispatch; // dva dispatch方法 +} + interface LocationState { isEdit?: boolean; editData?: SupplierTemplateManage.TemplateItem; @@ -57,7 +64,7 @@ interface LocationState { const { Title } = Typography; -const SupplierTemplateManageAdd: React.FC = () => { +const SupplierTemplateManageAdd: React.FC = ({ breadcrumb, dispatch }) => { const intl = useIntl(); const [form] = Form.useForm(); const [loading, setLoading] = useState(false); @@ -79,7 +86,12 @@ const SupplierTemplateManageAdd: React.FC = () => { try { const res = await getAllTemplates(); if (res.success && res.data) { - setTemplateList(res.data); + // 如果是修改,需要过滤掉自己 + if (location.state?.editData) { + setTemplateList(res.data.filter((template: SupplierTemplateManage.TemplateItem) => template.id !== location.state.editData?.id)); + } else { + setTemplateList(res.data); + } } else { message.error(intl.formatMessage({ id: 'supplierTemplateManage.message.getTemplateListFailed' }) || res.message); } @@ -122,6 +134,19 @@ const SupplierTemplateManageAdd: React.FC = () => { setLoading(false); } }; + useEffect(() => { + if (location.state?.editData?.id && dispatch) { + dispatch({ + type: 'breadcrumb/updateBreadcrumbName', + payload: intl.formatMessage({ id: "supplierTemplateManage.edit.title" }), + }); + } + return () => { + dispatch({ + type: 'breadcrumb/resetBreadcrumb', + }); + }; + }, [dispatch, intl, location]); // 初始化编辑数据 useEffect(() => { // 获取所有模板列表 @@ -450,4 +475,9 @@ const SupplierTemplateManageAdd: React.FC = () => { ); }; -export default SupplierTemplateManageAdd; + +// export default SupplierTemplateManageAdd; +// 将dva model中的状态映射到组件props +export default connect(({ breadcrumb }: { breadcrumb: BreadcrumbState }) => ({ + breadcrumb, +}))(SupplierTemplateManageAdd);