动态更新面包屑导航
This commit is contained in:
123
README.md
Normal file
123
README.md
Normal file
@ -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<PageProps> = ({ 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 (
|
||||
<div>
|
||||
{/* 页面内容... */}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 将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: <FormattedMessage id="menu.edit" /> }`
|
||||
|
||||
2. **重置面包屑**:确保在组件卸载时调用 `resetBreadcrumb` 以重置面包屑状态
|
||||
|
||||
3. **国际化处理**:使用 `intl.formatMessage()` 方法处理国际化文本,不要在 payload 中直接使用 FormattedMessage 组件
|
||||
|
||||
4. **依赖项管理**:确保 useEffect 的依赖项包含 `dispatch`, `intl` 和用于条件判断的变量(如 `location`)
|
@ -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 (
|
||||
<Breadcrumb>
|
||||
<Breadcrumb.Item
|
||||
onClick={() => {
|
||||
history.push('/');
|
||||
}}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
{intl.formatMessage({ id: 'menu.首页' })}
|
||||
<span style={{ cursor: 'pointer' }}>
|
||||
{intl.formatMessage({ id: 'menu.首页' })}
|
||||
</span>
|
||||
</Breadcrumb.Item>
|
||||
{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 (
|
||||
<Breadcrumb.Item key={item.path}>
|
||||
{intl.formatMessage({ id: `menu.${item.breadcrumbName}` || '' })}
|
||||
{displayName}
|
||||
</Breadcrumb.Item>
|
||||
);
|
||||
})}
|
||||
@ -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<BasicLayoutProps> = (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}
|
||||
</PageContainer>
|
||||
</ProLayout>
|
||||
</ConfigProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export default BasicLayout;
|
||||
export default connect(({ breadcrumb }: { breadcrumb: BreadcrumbState }) => ({
|
||||
breadcrumb
|
||||
}))(BasicLayout);
|
||||
|
||||
|
@ -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<LayoutIndexProps> = (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 (
|
||||
<>
|
||||
<Layout>
|
||||
<Header className="header">
|
||||
<HeaderComponent />
|
||||
</Header>
|
||||
<Layout>
|
||||
<Sider width={200} theme="light">
|
||||
<SiderMenu />
|
||||
</Sider>
|
||||
<Layout className="layout-content">
|
||||
<Breadcrumb style={{ margin: '10px 0' }}>
|
||||
{breadcrumb.breadcrumbs.map((breadcrumbItem, index) => (
|
||||
<Breadcrumb.Item key={breadcrumbItem.path}>
|
||||
{index < breadcrumb.breadcrumbs.length - 1 ? (
|
||||
<Link to={breadcrumbItem.path}>{breadcrumbItem.breadcrumbName}</Link>
|
||||
) : (
|
||||
breadcrumbItem.breadcrumbName
|
||||
)}
|
||||
</Breadcrumb.Item>
|
||||
))}
|
||||
</Breadcrumb>
|
||||
<Content>{children}</Content>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default connect(({ breadcrumb }: { breadcrumb: BreadcrumbModelState }) => ({
|
||||
breadcrumb,
|
||||
}))(LayoutIndex);
|
@ -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 ? (
|
||||
<Menu.SubMenu
|
||||
key={item.path}
|
||||
title={
|
||||
<Tooltip title={intl.formatMessage({ id: item.label })} placement="right">
|
||||
<>{intl.formatMessage({ id: item.label })}</>
|
||||
</Tooltip>
|
||||
}
|
||||
icon={<IconFont type={item.icon} />}
|
||||
>
|
||||
{renderMenuItems(item.children)}
|
||||
</Menu.SubMenu>
|
||||
) : (
|
||||
<Menu.Item key={item.path} icon={<IconFont type={item.icon} />}>
|
||||
<Tooltip title={intl.formatMessage({ id: item.label })} placement="right">
|
||||
<Link to={item.path}>{intl.formatMessage({ id: item.label })}</Link>
|
||||
</Tooltip>
|
||||
</Menu.Item>
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="header-menu">
|
||||
<Menu selectedKeys={[current]} mode="inline">
|
||||
{renderMenuItems(items)}
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
export default SiderMenu;
|
@ -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<any, void, unknown>;
|
||||
type Reducer<S> = (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<BreadcrumbModelState>;
|
||||
setBreadcrumbName: Reducer<BreadcrumbState>;
|
||||
resetState: Reducer<BreadcrumbState>;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成面包屑数据
|
||||
* @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;
|
@ -1,5 +1,8 @@
|
||||
import globalModal from './globalModal';
|
||||
import breadcrumb from './breadcrumb';
|
||||
|
||||
export default {
|
||||
globalModal,
|
||||
breadcrumb,
|
||||
// ... 其他 models
|
||||
};
|
||||
|
@ -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<PageProps> = ({ supplierAnnualTaskManage, dispatch }) => {
|
||||
const SupplierTaskManageAdd: React.FC<PageProps> = ({ supplierAnnualTaskManage, dispatch, breadcrumb }) => {
|
||||
const intl = useIntl();
|
||||
// 获取dva model中的状态
|
||||
const { currentStep, loading, detailLoading } = supplierAnnualTaskManage;
|
||||
@ -85,14 +86,23 @@ const SupplierTaskManageAdd: React.FC<PageProps> = ({ 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<PageProps> = ({ supplierAnnualTaskManage,
|
||||
|
||||
// 将dva model中的状态映射到组件props
|
||||
export default connect(
|
||||
({ supplierAnnualTaskManage }: { supplierAnnualTaskManage: SupplierTaskModelState }) => ({
|
||||
({ supplierAnnualTaskManage, breadcrumb }: { supplierAnnualTaskManage: SupplierTaskModelState, breadcrumb: BreadcrumbState }) => ({
|
||||
supplierAnnualTaskManage,
|
||||
breadcrumb,
|
||||
}),
|
||||
)(SupplierTaskManageAdd);
|
||||
|
@ -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<PageProps> = ({ breadcrumb, dispatch }) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const [form] = Form.useForm();
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [isEdit, setIsEdit] = useState<boolean>(false);
|
||||
const [templateDetail, setTemplateDetail] = useState<supplierAnnualTemplateManage.TemplateDetailData | null>(null);
|
||||
const [templateList, setTemplateList] = useState<supplierAnnualTemplateManage.TemplateRecord[]>([]);
|
||||
const [indicatorList, setIndicatorList] = useState<supplierAnnualTemplateManage.IndicatorItem[]>([]);
|
||||
const [templateDetail, setTemplateDetail] =
|
||||
useState<supplierAnnualTemplateManage.TemplateDetailData | null>(null);
|
||||
const [templateList, setTemplateList] = useState<supplierAnnualTemplateManage.TemplateRecord[]>(
|
||||
[],
|
||||
);
|
||||
const [indicatorList, setIndicatorList] = useState<supplierAnnualTemplateManage.IndicatorItem[]>(
|
||||
[],
|
||||
);
|
||||
|
||||
// 获取路由传递的数据
|
||||
const location = useLocation<LocationState>();
|
||||
@ -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 = () => {
|
||||
<Input
|
||||
value={text}
|
||||
onChange={(e) => 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}
|
||||
>
|
||||
<Option value={StarOptions.YES}>{intl.formatMessage({ id: 'supplierAnnualTemplateManage.common.yes' })}</Option>
|
||||
<Option value={StarOptions.NO}>{intl.formatMessage({ id: 'supplierAnnualTemplateManage.common.no' })}</Option>
|
||||
<Option value={StarOptions.YES}>
|
||||
{intl.formatMessage({ id: 'supplierAnnualTemplateManage.common.yes' })}
|
||||
</Option>
|
||||
<Option value={StarOptions.NO}>
|
||||
{intl.formatMessage({ id: 'supplierAnnualTemplateManage.common.no' })}
|
||||
</Option>
|
||||
</Select>
|
||||
),
|
||||
},
|
||||
@ -355,12 +400,19 @@ const SupplierAnnualTemplateManageAdd: React.FC = () => {
|
||||
/>
|
||||
)}
|
||||
<Popconfirm
|
||||
title={intl.formatMessage({ id: 'supplierAnnualTemplateManage.modal.deleteConfirmContent' })}
|
||||
title={intl.formatMessage({
|
||||
id: 'supplierAnnualTemplateManage.modal.deleteConfirmContent',
|
||||
})}
|
||||
onConfirm={() => handleDeleteIndicator(index)}
|
||||
okText={intl.formatMessage({ id: 'supplierAnnualTemplateManage.common.confirm' })}
|
||||
cancelText={intl.formatMessage({ id: 'supplierAnnualTemplateManage.common.cancel' })}
|
||||
>
|
||||
<Button type="link" danger icon={<DeleteOutlined />} title={intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.deleteIndicator' })} />
|
||||
<Button
|
||||
type="link"
|
||||
danger
|
||||
icon={<DeleteOutlined />}
|
||||
title={intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.deleteIndicator' })}
|
||||
/>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
),
|
||||
@ -369,143 +421,223 @@ const SupplierAnnualTemplateManageAdd: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div className="common-container">
|
||||
<div className={styles.pageHeader}>
|
||||
<Title level={4} style={{ margin: 0 }}>
|
||||
{isEdit
|
||||
? intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.edit' })
|
||||
: intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.title' })
|
||||
}
|
||||
</Title>
|
||||
<Button type="link" icon={<ArrowLeftOutlined />} onClick={handleBack}>
|
||||
{intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.back' })}
|
||||
</Button>
|
||||
<div className={styles.pageHeader}>
|
||||
<Title level={4} style={{ margin: 0 }}>
|
||||
{isEdit
|
||||
? intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.edit' })
|
||||
: intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.title' })}
|
||||
</Title>
|
||||
<Button type="link" icon={<ArrowLeftOutlined />} onClick={handleBack}>
|
||||
{intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.back' })}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={handleSubmit}
|
||||
initialValues={{
|
||||
categoryLimitation: CategoryLimitationType.UNIVERSAL,
|
||||
status: AnnualTemplateStatus.DRAFT,
|
||||
}}
|
||||
labelCol={{ span: 7 }}
|
||||
wrapperCol={{ span: 17 }}
|
||||
>
|
||||
<Spin spinning={loading}>
|
||||
<Card
|
||||
title={intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.basicInfo' })}
|
||||
bordered={false}
|
||||
className={styles.innerCard}
|
||||
>
|
||||
<Row gutter={24}>
|
||||
<Col span={8}>
|
||||
<Form.Item
|
||||
label={intl.formatMessage({
|
||||
id: 'supplierAnnualTemplateManage.list.templateName',
|
||||
})}
|
||||
name="templateName"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: `${intl.formatMessage({
|
||||
id: 'supplierAnnualTemplateManage.common.pleaseInput',
|
||||
})}${intl.formatMessage({
|
||||
id: 'supplierAnnualTemplateManage.list.templateName',
|
||||
})}`,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
placeholder={`${intl.formatMessage({
|
||||
id: 'supplierAnnualTemplateManage.common.pleaseInput',
|
||||
})}${intl.formatMessage({
|
||||
id: 'supplierAnnualTemplateManage.list.templateName',
|
||||
})}`}
|
||||
maxLength={50}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item
|
||||
label={intl.formatMessage({
|
||||
id: 'supplierAnnualTemplateManage.add.categoryLimitation',
|
||||
})}
|
||||
name="categoryLimitation"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: `${intl.formatMessage({
|
||||
id: 'supplierAnnualTemplateManage.common.pleaseSelect',
|
||||
})}${intl.formatMessage({
|
||||
id: 'supplierAnnualTemplateManage.add.categoryLimitation',
|
||||
})}`,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Radio.Group>
|
||||
<Radio value={CategoryLimitationType.UNIVERSAL}>
|
||||
{intl.formatMessage({
|
||||
id: 'supplierAnnualTemplateManage.add.categoryLimitationUniversal',
|
||||
})}
|
||||
</Radio>
|
||||
<Radio value={CategoryLimitationType.LIMITED}>
|
||||
{intl.formatMessage({
|
||||
id: 'supplierAnnualTemplateManage.add.categoryLimitationLimited',
|
||||
})}
|
||||
</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item
|
||||
noStyle
|
||||
shouldUpdate={(prevValues, currentValues) =>
|
||||
prevValues.categoryLimitation !== currentValues.categoryLimitation
|
||||
}
|
||||
>
|
||||
{({ getFieldValue }) => {
|
||||
const categoryLimitation = getFieldValue('categoryLimitation');
|
||||
return categoryLimitation === CategoryLimitationType.LIMITED ? (
|
||||
<Form.Item
|
||||
label={intl.formatMessage({
|
||||
id: 'supplierAnnualTemplateManage.add.selectCategory',
|
||||
})}
|
||||
name="categoryId"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: `${intl.formatMessage({
|
||||
id: 'supplierAnnualTemplateManage.common.pleaseSelect',
|
||||
})}${intl.formatMessage({
|
||||
id: 'supplierAnnualTemplateManage.list.category',
|
||||
})}`,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CategorySelector multiple={false} />
|
||||
</Form.Item>
|
||||
) : null;
|
||||
}}
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row gutter={24}>
|
||||
<Col span={8}>
|
||||
<Form.Item
|
||||
label={intl.formatMessage({
|
||||
id: 'supplierAnnualTemplateManage.add.selectTemplate',
|
||||
})}
|
||||
name="copyTemplateId"
|
||||
>
|
||||
<Select
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'supplierAnnualTemplateManage.common.pleaseSelect',
|
||||
})}
|
||||
loading={templateList.length === 0}
|
||||
onSelect={handleTemplateSelect}
|
||||
>
|
||||
{templateList.map((template) =>
|
||||
template.id ? (
|
||||
<Option key={template.id} value={template.id}>
|
||||
{template.templateName}
|
||||
</Option>
|
||||
) : null,
|
||||
)}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item
|
||||
label={intl.formatMessage({ id: 'supplierAnnualTemplateManage.list.status' })}
|
||||
name="status"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
message: `${intl.formatMessage({
|
||||
id: 'supplierAnnualTemplateManage.common.pleaseSelect',
|
||||
})}${intl.formatMessage({ id: 'supplierAnnualTemplateManage.list.status' })}`,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Radio.Group>
|
||||
<Radio value={AnnualTemplateStatus.DRAFT}>
|
||||
{AnnualTemplateStatusText[AnnualTemplateStatus.DRAFT]}
|
||||
</Radio>
|
||||
<Radio value={AnnualTemplateStatus.ENABLED}>
|
||||
{AnnualTemplateStatusText[AnnualTemplateStatus.ENABLED]}
|
||||
</Radio>
|
||||
<Radio value={AnnualTemplateStatus.DISABLED}>
|
||||
{AnnualTemplateStatusText[AnnualTemplateStatus.DISABLED]}
|
||||
</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Card
|
||||
title={intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.indicatorInfo' })}
|
||||
bordered={false}
|
||||
className={styles.innerCard}
|
||||
>
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={indicatorList}
|
||||
bordered
|
||||
rowKey="orderBy"
|
||||
size="middle"
|
||||
pagination={false}
|
||||
locale={{
|
||||
emptyText: intl.formatMessage({
|
||||
id: 'supplierAnnualTemplateManage.add.noIndicatorData',
|
||||
}),
|
||||
}}
|
||||
className={styles.indicatorTable}
|
||||
/>
|
||||
</Card>
|
||||
</Spin>
|
||||
|
||||
<div className={styles.formActions}>
|
||||
<Space>
|
||||
<Button onClick={handleBack}>
|
||||
{intl.formatMessage({ id: 'supplierAnnualTemplateManage.common.cancel' })}
|
||||
</Button>
|
||||
<Button type="primary" htmlType="submit" loading={loading} icon={<SaveOutlined />}>
|
||||
{isEdit
|
||||
? intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.update' })
|
||||
: intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.save' })}
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<Form
|
||||
form={form}
|
||||
onFinish={handleSubmit}
|
||||
initialValues={{
|
||||
categoryLimitation: CategoryLimitationType.UNIVERSAL,
|
||||
status: AnnualTemplateStatus.DRAFT,
|
||||
}}
|
||||
labelCol={{ span: 7 }}
|
||||
wrapperCol={{ span: 17 }}
|
||||
>
|
||||
<Spin spinning={loading}>
|
||||
<Card title={intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.basicInfo' })} bordered={false} className={styles.innerCard}>
|
||||
<Row gutter={24}>
|
||||
<Col span={8}>
|
||||
<Form.Item
|
||||
label={intl.formatMessage({ id: 'supplierAnnualTemplateManage.list.templateName' })}
|
||||
name="templateName"
|
||||
rules={[{ required: true, message: `${intl.formatMessage({ id: 'supplierAnnualTemplateManage.common.pleaseInput' })}${intl.formatMessage({ id: 'supplierAnnualTemplateManage.list.templateName' })}` }]}
|
||||
>
|
||||
<Input placeholder={`${intl.formatMessage({ id: 'supplierAnnualTemplateManage.common.pleaseInput' })}${intl.formatMessage({ id: 'supplierAnnualTemplateManage.list.templateName' })}`} maxLength={50} />
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item
|
||||
label={intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.categoryLimitation' })}
|
||||
name="categoryLimitation"
|
||||
rules={[{ required: true, message: `${intl.formatMessage({ id: 'supplierAnnualTemplateManage.common.pleaseSelect' })}${intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.categoryLimitation' })}` }]}
|
||||
>
|
||||
<Radio.Group>
|
||||
<Radio value={CategoryLimitationType.UNIVERSAL}>
|
||||
{intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.categoryLimitationUniversal' })}
|
||||
</Radio>
|
||||
<Radio value={CategoryLimitationType.LIMITED}>
|
||||
{intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.categoryLimitationLimited' })}
|
||||
</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item
|
||||
noStyle
|
||||
shouldUpdate={(prevValues, currentValues) =>
|
||||
prevValues.categoryLimitation !== currentValues.categoryLimitation
|
||||
}
|
||||
>
|
||||
{({ getFieldValue }) => {
|
||||
const categoryLimitation = getFieldValue('categoryLimitation');
|
||||
return categoryLimitation === CategoryLimitationType.LIMITED ? (
|
||||
<Form.Item
|
||||
label={intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.selectCategory' })}
|
||||
name="categoryId"
|
||||
rules={[{ required: true, message: `${intl.formatMessage({ id: 'supplierAnnualTemplateManage.common.pleaseSelect' })}${intl.formatMessage({ id: 'supplierAnnualTemplateManage.list.category' })}` }]}
|
||||
>
|
||||
<CategorySelector multiple={false} />
|
||||
</Form.Item>
|
||||
) : null;
|
||||
}}
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Row gutter={24}>
|
||||
<Col span={8}>
|
||||
<Form.Item
|
||||
label={intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.selectTemplate' })}
|
||||
name="copyTemplateId"
|
||||
>
|
||||
<Select
|
||||
placeholder={intl.formatMessage({ id: 'supplierAnnualTemplateManage.common.pleaseSelect' })}
|
||||
loading={templateList.length === 0}
|
||||
onSelect={handleTemplateSelect}
|
||||
>
|
||||
{templateList.map(template => (
|
||||
template.id ? (
|
||||
<Option key={template.id} value={template.id}>
|
||||
{template.templateName}
|
||||
</Option>
|
||||
) : null
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
<Col span={8}>
|
||||
<Form.Item
|
||||
label={intl.formatMessage({ id: 'supplierAnnualTemplateManage.list.status' })}
|
||||
name="status"
|
||||
rules={[{ required: true, message: `${intl.formatMessage({ id: 'supplierAnnualTemplateManage.common.pleaseSelect' })}${intl.formatMessage({ id: 'supplierAnnualTemplateManage.list.status' })}` }]}
|
||||
>
|
||||
<Radio.Group>
|
||||
<Radio value={AnnualTemplateStatus.DRAFT}>{AnnualTemplateStatusText[AnnualTemplateStatus.DRAFT]}</Radio>
|
||||
<Radio value={AnnualTemplateStatus.ENABLED}>{AnnualTemplateStatusText[AnnualTemplateStatus.ENABLED]}</Radio>
|
||||
<Radio value={AnnualTemplateStatus.DISABLED}>{AnnualTemplateStatusText[AnnualTemplateStatus.DISABLED]}</Radio>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Card title={intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.indicatorInfo' })} bordered={false} className={styles.innerCard}>
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={indicatorList}
|
||||
bordered
|
||||
rowKey="orderBy"
|
||||
size="middle"
|
||||
pagination={false}
|
||||
locale={{ emptyText: intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.noIndicatorData' }) }}
|
||||
className={styles.indicatorTable}
|
||||
/>
|
||||
</Card>
|
||||
</Spin>
|
||||
|
||||
<div className={styles.formActions}>
|
||||
<Space>
|
||||
<Button onClick={handleBack}>{intl.formatMessage({ id: 'supplierAnnualTemplateManage.common.cancel' })}</Button>
|
||||
<Button type="primary" htmlType="submit" loading={loading} icon={<SaveOutlined />}>
|
||||
{isEdit ? intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.update' }) : intl.formatMessage({ id: 'supplierAnnualTemplateManage.add.save' })}
|
||||
</Button>
|
||||
</Space>
|
||||
</div>
|
||||
</Form>
|
||||
</Form>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SupplierAnnualTemplateManageAdd;
|
||||
// export default SupplierAnnualTemplateManageAdd;
|
||||
// 将dva model中的状态映射到组件props
|
||||
export default connect(({ breadcrumb }: { breadcrumb: BreadcrumbState }) => ({
|
||||
breadcrumb,
|
||||
}))(SupplierAnnualTemplateManageAdd);
|
||||
|
@ -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<PageProps> = ({ supplierTaskManage, dispatch }) => {
|
||||
const SupplierTaskManageAdd: React.FC<PageProps> = ({
|
||||
supplierTaskManage,
|
||||
dispatch,
|
||||
breadcrumb,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
// 获取dva model中的状态
|
||||
const { currentStep, loading, detailLoading } = supplierTaskManage;
|
||||
@ -70,6 +75,11 @@ const SupplierTaskManageAdd: React.FC<PageProps> = ({ 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<PageProps> = ({ supplierTaskManage, dispat
|
||||
dispatch({
|
||||
type: 'supplierTaskManage/resetState',
|
||||
});
|
||||
dispatch({
|
||||
type: 'breadcrumb/resetBreadcrumb',
|
||||
});
|
||||
}
|
||||
};
|
||||
}, [dispatch]);
|
||||
@ -124,7 +137,9 @@ const SupplierTaskManageAdd: React.FC<PageProps> = ({ 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: <EvaluatorSelectStep ref={evaluatorFormRef} />,
|
||||
},
|
||||
{
|
||||
@ -177,7 +192,9 @@ const SupplierTaskManageAdd: React.FC<PageProps> = ({ 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<PageProps> = ({ supplierTaskManage, dispat
|
||||
|
||||
// 将dva model中的状态映射到组件props
|
||||
export default connect(
|
||||
({ supplierTaskManage }: { supplierTaskManage: SupplierTaskModelState }) => ({
|
||||
({
|
||||
supplierTaskManage,
|
||||
breadcrumb,
|
||||
}: {
|
||||
supplierTaskManage: SupplierTaskModelState;
|
||||
breadcrumb: BreadcrumbState;
|
||||
}) => ({
|
||||
supplierTaskManage,
|
||||
breadcrumb,
|
||||
}),
|
||||
)(SupplierTaskManageAdd);
|
||||
|
@ -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<PageProps> = ({ breadcrumb, dispatch }) => {
|
||||
const intl = useIntl();
|
||||
const [form] = Form.useForm<FormValues>();
|
||||
const [loading, setLoading] = useState<boolean>(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);
|
||||
|
Reference in New Issue
Block a user