umi#Link TypeScript Examples
The following examples show how to use
umi#Link.
You can vote up the ones you like or vote down the ones you don't like,
and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: list.tsx From yforms with MIT License | 6 votes |
function QueryParamsDemo() {
const query = useQuery();
const match = { params: { type: query.get('type'), id: query.get('id') } };
const hash = useLocation().hash.split('?')[0];
return (
<div>
{match.params.type ? (
<Child match={match} />
) : (
<div>
<h2>列表</h2>
<ul>
<li>
<Link to={`${hash}?type=create`}>新建</Link>
</li>
<li>
<Link to={`${hash}?type=view&id=1`}>查看</Link>
</li>
<li>
<Link to={`${hash}?type=edit&id=1`}>编辑</Link>
</li>
</ul>
</div>
)}
</div>
);
}
Example #2
Source File: index.tsx From fower with MIT License | 6 votes |
export default function IndexPage() {
const [colorMode, setColorMode] = useState('default');
return (
<Box p10>
<Box toCenter gray500--dark spaceX2 flexWrap gap-10>
{pages.map((i) => (
<Link key={i} to={'/' + i}>
{i}
</Link>
))}
</Box>
<Box
as="button"
onClick={() => {
setColorMode(colorMode === 'default' ? 'dark' : 'default');
if (colorMode === 'default') {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}}
>
切换 {colorMode}
</Box>
</Box>
);
}
Example #3
Source File: tag-list.tsx From bext with MIT License | 6 votes |
TagList: FC = () => {
const { tagList } = useMeta();
const theme = useTheme();
return (
<>
<Title>全部分类</Title>
<div className="grid grid-cols-3 gap-3">
{tagList.map((tag) => (
<Link
to={`/meta?tag=${encodeURIComponent(tag.name)}`}
key={tag.name}
onClick={() => trackEvent(Events.tagClick, tag.name)}
>
<div
className="h-12 flex items-center justify-center cursor-pointer"
style={{
boxShadow: theme.effects.elevation4,
}}
>
{tag.name}
</div>
</Link>
))}
</div>
</>
);
}
Example #4
Source File: BasicLayout.tsx From ant-design-pro-V4 with MIT License | 6 votes |
noMatch = ( <Result status={403} title="403" subTitle="Sorry, you are not authorized to access this page." extra={ <Button type="primary"> <Link to="/user/login">Go Login</Link> </Button> } /> )
Example #5
Source File: BasicLayout.tsx From ui-visualization with MIT License | 6 votes |
noMatch = ( <Result status={403} title="403" subTitle="Sorry, you are not authorized to access this page." extra={ <Button type="primary"> <Link to="/user/login">Go Login</Link> </Button> } /> )
Example #6
Source File: BasicLayout.tsx From jetlinks-ui-antd with MIT License | 6 votes |
noMatch = ( <Result status="403" title="403" subTitle="Sorry, you are not authorized to access this page." extra={ <Button type="primary"> <Link to="/user/login">Go Login</Link> </Button> } /> )
Example #7
Source File: index.tsx From umi-micro-apps with MIT License | 6 votes |
UserList = (props: any) => {
const { shopId } = props;
const { data = [] } = useRequest(() => request(`/api/user/list?shopId=${shopId}`));
const columns = [
{
dataIndex: 'id',
title: 'ID',
},
{
dataIndex: 'name',
title: '姓名',
},
{
dataIndex: 'address',
title: '住址',
},
{
dataIndex: 'id',
title: '操作',
render: (id: string) => (
<Link to={`/${id}`}>详情</Link>
)
},
];
return (
<div>
<h1 style={{ marginBottom: 24 }}>用户列表</h1>
<Table rowKey="id" columns={columns} dataSource={data} />
</div>
);
}
Example #8
Source File: UserLayout.tsx From jetlinks-ui-antd with MIT License | 5 votes |
UserLayout: React.FC<UserLayoutProps> = props => {
const {
route = {
routes: [],
},
settings,
} = props;
const { routes = [] } = route;
const {
children,
location = {
pathname: '',
},
} = props;
const { breadcrumb } = getMenuData(routes);
const title = getPageTitle({
pathname: location.pathname,
breadcrumb,
...props,
});
return (
<>
<Helmet>
<title>{title}</title>
<meta name="description" content={title} />
</Helmet>
<div className={styles.container}>
<div className={styles.lang}>
{/* <SelectLang /> */}
</div>
<div className={styles.content}>
<div className={styles.top}>
<div className={styles.header}>
<Link to="/">
<img alt="logo" className={styles.logo} src={logo} />
<span className={styles.title}>JetLinks </span>
</Link>
</div>
<div className={styles.desc}>捷联物联网平台</div>
</div>
{children}
</div>
{/* <DefaultFooter /> */}
</div>
</>
);
}
Example #9
Source File: index.tsx From plugin-layout with MIT License | 5 votes |
BasicLayout = (props: any) => {
const { children, userConfig, location } = props;
const { initialState, loading } = (useModel && useModel('@@initialState')) || {
initialState: undefined,
loading: false,
}; // plugin-initial-state 未开启
const _routes = require('@@/router').routes;
const rightContentRender = useRightContent(userConfig, loading, initialState);
const layoutConfig = getLayoutConfigFromRoute(_routes);
const patchMenus: (ms: MenuItem[]) => MenuItem[] =
userConfig.patchMenus || ((ms: MenuItem[]): MenuItem[] => ms);
const menus = patchMenus(getMenuDataFromRoutes(_routes[0].routes));
// layout 是否渲染相关
const pathName = location.pathname;
const layoutRender: any = {};
// 动态路由匹配
const currentMatchPaths = Object.keys(layoutConfig).filter(item =>
pathToRegexp(`${item}(.*)`).test(pathName),
);
const currentPathConfig = currentMatchPaths.length
? layoutConfig[currentMatchPaths[currentMatchPaths.length - 1]]
: undefined;
if (currentPathConfig && currentPathConfig.hideMenu) {
layoutRender.menuRender = false;
}
if (currentPathConfig && currentPathConfig.hideNav) {
layoutRender.headerRender = false;
}
return (
<ProLayout
title={userConfig.name || userConfig.title}
className="umi-plugin-layout-main"
navTheme="dark"
siderWidth={256}
onMenuHeaderClick={e => {
e.stopPropagation();
e.preventDefault();
history.push('/');
}}
menu={{ locale: userConfig.locale }}
menuDataRender={() => menus}
formatMessage={formatMessage}
logo={initialState?.avatar || logo}
menuItemRender={(menuItemProps, defaultDom) => {
if (menuItemProps.isUrl || menuItemProps.children) {
return defaultDom;
}
return <Link to={menuItemProps.path || ''}>{defaultDom}</Link>;
}}
disableContentMargin
rightContentRender={rightContentRender}
fixSiderbar
fixedHeader
{...userConfig}
{...props}
{...layoutRender}
>
<ErrorBoundary>
<WithExceptionOpChildren currentPathConfig={currentPathConfig}>
{userConfig.childrenRender
? userConfig.childrenRender(children)
: children}
</WithExceptionOpChildren>
</ErrorBoundary>
</ProLayout>
);
}
Example #10
Source File: LoginLayout.tsx From dashboard with Apache License 2.0 | 5 votes |
LoginLayout: React.FC<UserLayoutProps> = (props) => {
const {
route = {
routes: [],
},
} = props;
const { routes = [] } = route;
const {
children,
location = {
pathname: '',
},
} = props;
const { breadcrumb } = getMenuData(routes);
const title = getPageTitle({
pathname: location.pathname,
breadcrumb,
...props,
});
return (
<HelmetProvider>
<Helmet>
<title>{title}</title>
<meta name="description" content={title} />
</Helmet>
<div className={styles.container}>
<div className={styles.content}>
<div className={styles.top}>
<div className={styles.header}>
<Link to="/">
<img alt="logo" className={styles.logo} src={logo} />
</Link>
</div>
</div>
{children}
</div>
</div>
</HelmetProvider>
);
}
Example #11
Source File: UserLayout.tsx From ui-visualization with MIT License | 5 votes |
UserLayout: React.FC<UserLayoutProps> = (props) => {
const {
route = {
routes: [],
},
} = props;
const { routes = [] } = route;
const {
children,
location = {
pathname: '',
},
} = props;
const { formatMessage } = useIntl();
const { breadcrumb } = getMenuData(routes);
const title = getPageTitle({
pathname: location.pathname,
formatMessage,
breadcrumb,
...props,
});
return (
<HelmetProvider>
<Helmet>
<title>{title}</title>
<meta name="description" content={title} />
</Helmet>
<div className={styles.container}>
<div className={styles.lang}>
<SelectLang />
</div>
<div className={styles.content}>
<div className={styles.top}>
<div className={styles.header}>
<Link to="/">
<img alt="logo" className={styles.logo} src={logo} />
<span className={styles.title}>Ant Design</span>
</Link>
</div>
<div className={styles.desc}>Ant Design 是西湖区最具影响力的 Web 设计规范</div>
</div>
{children}
</div>
<DefaultFooter />
</div>
</HelmetProvider>
);
}
Example #12
Source File: UserLayout.tsx From ant-design-pro-V4 with MIT License | 5 votes |
UserLayout: React.FC<UserLayoutProps> = (props) => {
const {
route = {
routes: [],
},
} = props;
const { routes = [] } = route;
const {
children,
location = {
pathname: '',
},
} = props;
const { formatMessage } = useIntl();
const { breadcrumb } = getMenuData(routes);
const title = getPageTitle({
pathname: location.pathname,
formatMessage,
breadcrumb,
...props,
});
return (
<HelmetProvider>
<Helmet>
<title>{title}</title>
<meta name="description" content={title} />
</Helmet>
<div className={styles.container}>
<div className={styles.lang}>
<SelectLang />
</div>
<div className={styles.content}>
<div className={styles.top}>
<div className={styles.header}>
<Link to="/">
<img alt="logo" className={styles.logo} src={logo} />
<span className={styles.title}>Ant Design</span>
</Link>
</div>
<div className={styles.desc}>
<FormattedMessage
id="pages.layouts.userLayout.title"
defaultMessage="Ant Design. The most influential Web design specification in Xihu District."
/>
</div>
</div>
{children}
</div>
<DefaultFooter />
</div>
</HelmetProvider>
);
}
Example #13
Source File: BasicLayout.tsx From jetlinks-ui-antd with MIT License | 4 votes |
BasicLayout: React.FC<BasicLayoutProps> = props => {
const {
dispatch,
children,
settings,
location = {
pathname: '/',
},
} = props;
const [mark, setMark] = useState<string | boolean>(localStorage.getItem('hide_menu') || "false");
const hide_menu = props.location?.query?.hide_menu;
const token = getAccessToken();
useEffect(() => {
if (dispatch) {
if(token!=='null'){
dispatch({
type: 'user/fetchCurrent',
});
}else{
router.push('/user/login');
}
}
if (hide_menu) {
setMark(hide_menu);
localStorage.setItem('hide_menu', hide_menu);
}
}, [hide_menu]);
const handleMenuCollapse = (payload: boolean): void => {
if (dispatch) {
dispatch({
type: 'global/changeLayoutCollapsed',
payload,
});
}
}; // get children authority
const authorized = getAuthorityFromRouter(props.route.routes, location.pathname || '/') || {
authority: undefined,
};
return mark === 'true' ? (
<Authorized authority={authorized!.authority} noMatch={noMatch}>
{children}
</Authorized>
) : (
<ProLayout
// logo={logo}
logo={settings.titleIcon || logo}
menuHeaderRender={(logoDom, titleDom) => (
<Link to="/">
{logoDom}
{titleDom}
</Link>
)}
onCollapse={handleMenuCollapse}
menuItemRender={(menuItemProps, defaultDom) => {
if (menuItemProps.isUrl || menuItemProps.children || !menuItemProps.path) {
return defaultDom;
}
return <Link to={menuItemProps.path}>{defaultDom}</Link>;
}}
breadcrumbRender={(routers = []) => [
{
path: '/',
breadcrumbName: '首页',
},
...routers,
]}
itemRender={(route, params, routes, paths) => {
const first = routes.indexOf(route) === 0;
return first ? (
<Link to={paths.join('/')}>{route.breadcrumbName}</Link>
) : (
<span>{route.breadcrumbName}</span>
);
}}
footerRender={footerRender}
menuDataRender={menuDataRender}
// menuDataRender={()=>menuData}
rightContentRender={() => <RightContent />}
{...props}
{...settings}
fixSiderbar
>
<Authorized authority={authorized!.authority} noMatch={noMatch}>
{children}
</Authorized>
</ProLayout>
);
}
Example #14
Source File: index.tsx From ant-design-pro-V4 with MIT License | 4 votes |
DashboardWorkplace: FC = () => {
const { loading: projectLoading, data: projectNotice = [] } = useRequest(queryProjectNotice);
const { loading: activitiesLoading, data: activities = [] } = useRequest(queryActivities);
const { data } = useRequest(fakeChartData);
const renderActivities = (item: ActivitiesType) => {
const events = item.template.split(/@\{([^{}]*)\}/gi).map((key) => {
if (item[key]) {
return (
<a href={item[key].link} key={item[key].name}>
{item[key].name}
</a>
);
}
return key;
});
return (
<List.Item key={item.id}>
<List.Item.Meta
avatar={<Avatar src={item.user.avatar} />}
title={
<span>
<a className={styles.username}>{item.user.name}</a>
<span className={styles.event}>{events}</span>
</span>
}
description={
<span className={styles.datetime} title={item.updatedAt}>
{moment(item.updatedAt).fromNow()}
</span>
}
/>
</List.Item>
);
};
return (
<PageContainer
content={
<PageHeaderContent
currentUser={{
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png',
name: '吴彦祖',
userid: '00000001',
email: '[email protected]',
signature: '海纳百川,有容乃大',
title: '交互专家',
group: '蚂蚁金服-某某某事业群-某某平台部-某某技术部-UED',
}}
/>
}
extraContent={<ExtraContent />}
>
<Row gutter={24}>
<Col xl={16} lg={24} md={24} sm={24} xs={24}>
<Card
className={styles.projectList}
style={{ marginBottom: 24 }}
title="进行中的项目"
bordered={false}
extra={<Link to="/">全部项目</Link>}
loading={projectLoading}
bodyStyle={{ padding: 0 }}
>
{projectNotice.map((item) => (
<Card.Grid className={styles.projectGrid} key={item.id}>
<Card bodyStyle={{ padding: 0 }} bordered={false}>
<Card.Meta
title={
<div className={styles.cardTitle}>
<Avatar size="small" src={item.logo} />
<Link to={item.href}>{item.title}</Link>
</div>
}
description={item.description}
/>
<div className={styles.projectItemContent}>
<Link to={item.memberLink}>{item.member || ''}</Link>
{item.updatedAt && (
<span className={styles.datetime} title={item.updatedAt}>
{moment(item.updatedAt).fromNow()}
</span>
)}
</div>
</Card>
</Card.Grid>
))}
</Card>
<Card
bodyStyle={{ padding: 0 }}
bordered={false}
className={styles.activeCard}
title="动态"
loading={activitiesLoading}
>
<List<ActivitiesType>
loading={activitiesLoading}
renderItem={(item) => renderActivities(item)}
dataSource={activities}
className={styles.activitiesList}
size="large"
/>
</Card>
</Col>
<Col xl={8} lg={24} md={24} sm={24} xs={24}>
<Card
style={{ marginBottom: 24 }}
title="快速开始 / 便捷导航"
bordered={false}
bodyStyle={{ padding: 0 }}
>
<EditableLinkGroup onAdd={() => {}} links={links} linkElement={Link} />
</Card>
<Card
style={{ marginBottom: 24 }}
bordered={false}
title="XX 指数"
loading={data?.radarData?.length === 0}
>
<div className={styles.chart}>
<Radar
height={343}
data={data?.radarData || []}
angleField="label"
seriesField="name"
radiusField="value"
area={{
visible: false,
}}
point={{
visible: true,
}}
legend={{
position: 'bottom-center',
}}
/>
</div>
</Card>
<Card
bodyStyle={{ paddingTop: 12, paddingBottom: 12 }}
bordered={false}
title="团队"
loading={projectLoading}
>
<div className={styles.members}>
<Row gutter={48}>
{projectNotice.map((item) => (
<Col span={12} key={`members-item-${item.id}`}>
<Link to={item.href}>
<Avatar src={item.logo} size="small" />
<span className={styles.member}>{item.member}</span>
</Link>
</Col>
))}
</Row>
</div>
</Card>
</Col>
</Row>
</PageContainer>
);
}
Example #15
Source File: BasicLayout.tsx From ui-visualization with MIT License | 4 votes |
BasicLayout: React.FC<BasicLayoutProps> = (props) => {
const {
dispatch,
children,
settings,
location = {
pathname: '/',
},
} = props;
/**
* constructor
*/
useEffect(() => {
if (dispatch) {
dispatch({
type: 'user/fetchCurrent',
});
}
}, []);
/**
* init variables
*/
const handleMenuCollapse = (payload: boolean): void => {
if (dispatch) {
dispatch({
type: 'global/changeLayoutCollapsed',
payload,
});
}
}; // get children authority
const authorized = getAuthorityFromRouter(props.route.routes, location.pathname || '/') || {
authority: undefined,
};
const { formatMessage } = useIntl();
return (
<ProLayout
logo={logo}
formatMessage={formatMessage}
menuHeaderRender={(logoDom, titleDom) => (
<Link to="/">
{logoDom}
{titleDom}
</Link>
)}
onCollapse={handleMenuCollapse}
menuItemRender={(menuItemProps, defaultDom) => {
if (menuItemProps.isUrl || menuItemProps.children || !menuItemProps.path) {
return defaultDom;
}
return <Link to={menuItemProps.path}>{defaultDom}</Link>;
}}
breadcrumbRender={(routers = []) => [
{
path: '/',
breadcrumbName: formatMessage({ id: 'menu.home' }),
},
...routers,
]}
itemRender={(route, params, routes, paths) => {
const first = routes.indexOf(route) === 0;
return first ? (
<Link to={paths.join('/')}>{route.breadcrumbName}</Link>
) : (
<span>{route.breadcrumbName}</span>
);
}}
footerRender={() => defaultFooterDom}
menuDataRender={menuDataRender}
rightContentRender={() => <RightContent />}
{...props}
{...settings}
>
<Authorized authority={authorized!.authority} noMatch={noMatch}>
{children}
</Authorized>
</ProLayout>
);
}
Example #16
Source File: index.tsx From ui-visualization with MIT License | 4 votes |
Login: React.FC<LoginProps> = (props) => {
const { userLogin = {}, submitting } = props;
const { status, type: loginType } = userLogin;
const [autoLogin, setAutoLogin] = useState(true);
const [type, setType] = useState<string>('account');
const handleSubmit = (values: LoginParamsType) => {
const { dispatch } = props;
dispatch({
type: 'login/login',
payload: { ...values, type },
});
};
return (
<div className={styles.main}>
<LoginForm activeKey={type} onTabChange={setType} onSubmit={handleSubmit}>
<Tab key="account" tab="账户密码登录">
{status === 'error' && loginType === 'account' && !submitting && (
<LoginMessage content="账户或密码错误(admin/ant.design)" />
)}
<UserName
name="userName"
placeholder="用户名: admin or user"
rules={[
{
required: true,
message: '请输入用户名!',
},
]}
/>
<Password
name="password"
placeholder="密码: ant.design"
rules={[
{
required: true,
message: '请输入密码!',
},
]}
/>
</Tab>
<Tab key="mobile" tab="手机号登录">
{status === 'error' && loginType === 'mobile' && !submitting && (
<LoginMessage content="验证码错误" />
)}
<Mobile
name="mobile"
placeholder="手机号"
rules={[
{
required: true,
message: '请输入手机号!',
},
{
pattern: /^1\d{10}$/,
message: '手机号格式错误!',
},
]}
/>
<Captcha
name="captcha"
placeholder="验证码"
countDown={120}
getCaptchaButtonText=""
getCaptchaSecondText="秒"
rules={[
{
required: true,
message: '请输入验证码!',
},
]}
/>
</Tab>
<div>
<Checkbox checked={autoLogin} onChange={(e) => setAutoLogin(e.target.checked)}>
自动登录
</Checkbox>
<a
style={{
float: 'right',
}}
>
忘记密码
</a>
</div>
<Submit loading={submitting}>登录</Submit>
<div className={styles.other}>
其他登录方式
<AlipayCircleOutlined className={styles.icon} />
<TaobaoCircleOutlined className={styles.icon} />
<WeiboCircleOutlined className={styles.icon} />
<Link className={styles.register} to="/user/register">
注册账户
</Link>
</div>
</LoginForm>
</div>
);
}
Example #17
Source File: BasicLayout.tsx From dashboard with Apache License 2.0 | 4 votes |
BasicLayout: React.FC<BasicLayoutProps> = (props) => {
const {
dispatch,
children,
settings,
location = {
pathname: '/',
},
} = props;
const menuDataRef = useRef<MenuDataItem[]>([]);
/** Init variables */
const handleMenuCollapse = (payload: boolean): void => {
if (dispatch) {
dispatch({
type: 'global/changeLayoutCollapsed',
payload,
});
}
}; // get children authority
const noMatch = (
<Result
status={403}
title="403"
subTitle="抱歉,您无权访问此页面"
extra={
<Button type="primary">
<Link to={'staffAdmin/login'}>
去登陆
</Link>
</Button>
}
/>
);
const authorized = useMemo(
() =>
getMatchMenu(location.pathname || '/', menuDataRef.current).pop() || {
authority: undefined,
},
[location.pathname],
);
return (
<ProLayout
logo={logo}
{...props}
{...settings}
onCollapse={handleMenuCollapse}
layout={'mix'}
navTheme={'light'}
headerTheme={'dark'}
siderWidth={260}
className={'main'}
menu={{
defaultOpenAll: true,
type: 'sub',
ignoreFlatMenu: true,
}}
onMenuHeaderClick={() => history.push('/')}
headerTitleRender={() => {
return (
<a onClick={() => history.push('/')}>
<img src={headerLogo} height={32} alt={'OpenSCRM'} />
</a>
);
}}
menuRender={(menuProps: HeaderViewProps, defaultDom: React.ReactNode) => {
return <div className={'sidebar-menu'}>{defaultDom}</div>;
}}
menuItemRender={(menuItemProps, defaultDom) => {
const isTopLevel = menuItemProps && menuItemProps?.pro_layout_parentKeys?.length === 0;
if (
menuItemProps.isUrl ||
!menuItemProps.path ||
location.pathname === menuItemProps.path
) {
return (
<div>
{!isTopLevel && getIcon(menuItemProps.icon)}
{defaultDom}
</div>
);
}
return (
<Link to={menuItemProps.path}>
{!isTopLevel && getIcon(menuItemProps.icon)}
{defaultDom}
</Link>
);
}}
// breadcrumbRender={(routers = []) => [
// {
// path: '/',
// breadcrumbName: '首页',
// },
// ...routers,
// ]}
breadcrumbRender={false}
footerRender={() => {
// if (settings.footerRender || settings.footerRender === undefined) {
// return defaultFooterDom;
// }
return null;
}}
menuDataRender={menuDataRender}
rightContentRender={() => <RightContent />}
postMenuData={(menuData) => {
menuDataRef.current = menuData || [];
return menuData || [];
}}
>
<Authorized authority={authorized!.authority} noMatch={noMatch}>
{children}
</Authorized>
</ProLayout>
);
}
Example #18
Source File: BasicLayout.tsx From ant-design-pro-V4 with MIT License | 4 votes |
BasicLayout: React.FC<BasicLayoutProps> = (props) => {
const {
dispatch,
children,
settings,
location = {
pathname: '/',
},
} = props;
const menuDataRef = useRef<MenuDataItem[]>([]);
useEffect(() => {
if (dispatch) {
dispatch({
type: 'user/fetchCurrent',
});
}
}, []);
/** Init variables */
const handleMenuCollapse = (payload: boolean): void => {
if (dispatch) {
dispatch({
type: 'global/changeLayoutCollapsed',
payload,
});
}
};
// get children authority
const authorized = useMemo(
() =>
getMatchMenu(location.pathname || '/', menuDataRef.current).pop() || {
authority: undefined,
},
[location.pathname],
);
const { formatMessage } = useIntl();
return (
<ProLayout
logo={logo}
formatMessage={formatMessage}
{...props}
{...settings}
onCollapse={handleMenuCollapse}
onMenuHeaderClick={() => history.push('/')}
menuItemRender={(menuItemProps, defaultDom) => {
if (
menuItemProps.isUrl ||
!menuItemProps.path ||
location.pathname === menuItemProps.path
) {
return defaultDom;
}
return <Link to={menuItemProps.path}>{defaultDom}</Link>;
}}
breadcrumbRender={(routers = []) => [
{
path: '/',
breadcrumbName: formatMessage({ id: 'menu.home' }),
},
...routers,
]}
itemRender={(route, params, routes, paths) => {
const first = routes.indexOf(route) === 0;
return first ? (
<Link to={paths.join('/')}>{route.breadcrumbName}</Link>
) : (
<span>{route.breadcrumbName}</span>
);
}}
footerRender={() => {
if (settings.footerRender || settings.footerRender === undefined) {
return defaultFooterDom;
}
return null;
}}
menuDataRender={menuDataRender}
rightContentRender={() => <RightContent />}
postMenuData={(menuData) => {
menuDataRef.current = menuData || [];
return menuData || [];
}}
waterMarkProps={{
content: 'Ant Design Pro',
fontColor: 'rgba(24,144,255,0.15)',
}}
>
<Authorized authority={authorized!.authority} noMatch={noMatch}>
{children}
</Authorized>
</ProLayout>
);
}
Example #19
Source File: form.tsx From dashboard with Apache License 2.0 | 4 votes |
ContactWayForm: React.FC<ContactWayFormProps> = (props) => {
const {staffs, tagGroups, initialValues, mode, ...rest} = props;
const [staffSelectionVisible, setStaffSelectionVisible] = useState(false);
const [selectedStaffs, setSelectedStaffs] = useState<StaffOption[]>([]);
const [backupStaffSelectionVisible, setBackupStaffSelectionVisible] = useState(false);
const [backupSelectedStaffs, setBackupSelectedStaffs] = useState<StaffOption[]>([]);
const [blockNicknames, setBlockNicknames] = useState<string[]>([]);
const [isFetchDone, setIsFetchDone] = useState(false);
const [autoReply, setAutoReply] = useState<WelcomeMsg>({text: ''});
const formRef = useRef<FormInstance>();
const backupSelectRef = useRef<HTMLInputElement>(null);
const staffSelectRef = useRef<HTMLInputElement>(null);
const addScheduleRef = useRef<HTMLInputElement>(null);
const [currentItem, setCurrentItem] = useState<ContactWayItem>();
const itemDataToFormValues = (item: ContactWayItem | any): CreateContactWayParam => {
const values: CreateContactWayParam = {
daily_add_customer_limit: 0,
...item,
id: props.itemID,
};
if (item.staffs) {
setSelectedStaffs(
item.staffs.map((staff: any) => {
return {
key: staff.ext_staff_id,
label: staff.id,
value: staff.ext_staff_id,
...staff,
ext_id: staff.ext_staff_id,
};
}),
);
}
if (item.auto_reply) {
setAutoReply(item.auto_reply);
}
if (item.nickname_block_list) {
setBlockNicknames(item.nickname_block_list);
}
if (item.backup_staffs) {
setBackupSelectedStaffs(
item.backup_staffs.map((staff: any) => {
return {
key: staff.ext_staff_id,
label: staff.id,
value: staff.ext_staff_id,
...staff,
ext_id: staff.ext_staff_id,
};
}),
);
}
const invertWeekdaysEnum = _.invert(WeekdaysEnum);
if (item.schedules) {
values.schedules = item.schedules.map((schedule: any) => {
return {
...schedule,
ext_staff_ids: schedule?.staffs?.map((staff: any) => staff.ext_staff_id),
weekdays: schedule?.weekdays?.map((day: string) => invertWeekdaysEnum[day]),
start_time: moment(schedule?.start_time, TimeLayout),
end_time: moment(schedule?.end_time, TimeLayout),
};
});
}
// 将内置的boolean转为后端约定的数字
Object.keys(values).forEach((key) => {
const t = typeof values[key];
if (
!['schedule_enable', 'auto_skip_verify_enable'].includes(key) &&
t === 'number' &&
key.includes('_enable') &&
[1, 2].includes(values[key])
) {
values[key] = values[key] === True;
}
});
if (!values.customer_tag_ext_ids) {
values.customer_tag_ext_ids = [];
}
values.skip_verify_start_time = moment(values.skip_verify_start_time, TimeLayout);
values.skip_verify_end_time = moment(values.skip_verify_end_time, TimeLayout);
return values;
};
useEffect(() => {
if (['edit', 'copy'].includes(mode) && props?.itemID) {
const hide = message.loading('加载数据中');
GetDetail(props?.itemID).then((res) => {
hide();
if (res.code === 0) {
setCurrentItem(res.data);
formRef.current?.setFieldsValue(itemDataToFormValues(res.data));
setIsFetchDone(true);
setAutoReply(res.data.auto_reply);
} else {
message.error(res.message);
}
});
}
}, [mode, props?.itemID]);
// 校验参数
const checkForm = (params: any): boolean => {
if (backupSelectedStaffs.length === 0) {
message.warning('请添加备份员工');
backupSelectRef?.current?.focus();
return false;
}
if (params.schedule_enable === Disable) {
if (selectedStaffs.length === 0) {
message.warning('请绑定员工');
staffSelectRef?.current?.focus();
return false;
}
}
if (params.schedule_enable === Enable) {
if (params.schedules.length === 0) {
message.warning('请添加工作周期');
addScheduleRef?.current?.focus();
return false;
}
}
return true;
};
// 格式化参数
const formatParams = (origin: any): CreateContactWayParam => {
const out = {...origin, id: props.itemID};
// 将内置的boolean转为后端约定的数字
Object.keys(out).forEach((key) => {
const t = typeof out[key];
if (t === 'boolean') {
out[key] = out[key] ? True : False;
}
});
const refStaffMap = _.keyBy(currentItem?.staffs, 'ext_staff_id');
out.staffs = selectedStaffs.map((item): StaffParam => {
return {
id: refStaffMap[item.ext_id]?.id || '',
ext_staff_id: item.ext_id,
daily_add_customer_limit: out.daily_add_customer_limit ? out.daily_add_customer_limit : 0,
};
});
const refBackupStaffMap = _.keyBy(currentItem?.backup_staffs, 'ext_staff_id');
out.backup_staffs = backupSelectedStaffs.map((item): StaffParam => {
return {
id: refBackupStaffMap[item.ext_id]?.id || '',
ext_staff_id: item.ext_id,
daily_add_customer_limit: out.daily_add_customer_limit ? out.daily_add_customer_limit : 0,
};
});
if (out.nickname_block_enable === True) {
out.nickname_block_list = blockNicknames;
}
if (out.skip_verify_start_time) {
out.skip_verify_start_time = moment(out.skip_verify_start_time, 'HH:mm')
.format('HH:mm:ss')
.toString();
}
if (out.skip_verify_end_time) {
out.skip_verify_end_time = moment(out.skip_verify_end_time, 'HH:mm')
.format('HH:mm:ss')
.toString();
}
if (out.schedules) {
out.schedules = out.schedules.map((schedule: any, index: number) => {
// @ts-ignore
const refScheduleStaffMap = _.keyBy(currentItem?.schedules[index]?.staffs, 'ext_staff_id');
return {
...schedule,
end_time: moment(schedule.end_time, DateTimeLayout).format('HH:mm:ss').toString(),
start_time: moment(schedule.start_time, DateTimeLayout).format('HH:mm:ss').toString(),
staffs: schedule.ext_staff_ids.map((ext_id: string) => {
return {
id: refScheduleStaffMap[ext_id]?.id ? refScheduleStaffMap[ext_id]?.id : '',
daily_add_customer_limit: 0,
ext_staff_id: ext_id,
};
}),
weekdays: schedule.weekdays.map((day: string) => {
return WeekdaysEnum[day];
}),
};
});
}
if (autoReply) {
out.auto_reply = autoReply;
}
return out;
};
return (
<>
<ProForm<ContactWayItem>
{...rest}
layout={'horizontal'}
labelCol={{
md: 4,
}}
className={styles.content}
formRef={formRef}
onFinish={async (values: any) => {
const params = formatParams(values);
if (!checkForm(params)) {
return false;
}
if (props.onFinish) {
return props.onFinish(params);
}
return false;
}}
initialValues={{...defaultValues, ...initialValues}}
>
<h3>基础信息</h3>
<Divider/>
<div className={styles.section}>
<ProFormText
labelAlign={'right'}
width={'md'}
name='name'
label='渠道码名称'
placeholder='请输入名称'
rules={[
{
required: true,
message: '请输入名称',
},
]}
/>
<ProFormSelect
width={'md'}
name='group_id'
label='渠道码分组'
placeholder='请选择分组'
request={async () => {
const res = await QueryGroup();
if (res.data) {
const items = res.data.items as ContactWayGroupItem[];
return items.map((item) => {
return {label: item.name, value: item.id};
});
}
return [];
}}
rules={[
{
required: true,
message: '请选择分组',
},
]}
/>
<Form.Item
label={<span className={'form-item-required'}>备份员工</span>}
tooltip={'此渠道码没有活跃员工时,使用此处配置的员工兜底'}
>
<Button
ref={backupSelectRef}
icon={<PlusOutlined/>}
onClick={() => setBackupStaffSelectionVisible(true)}
>
添加备用员工
</Button>
</Form.Item>
<Space style={{marginTop: -12, marginBottom: 24, marginLeft: 20}}>
{backupSelectedStaffs.map((item) => (
<div key={item.id} className={'staff-item'}>
<Badge
count={
<CloseCircleOutlined
onClick={() => {
setBackupSelectedStaffs(
backupSelectedStaffs.filter((staff) => staff.id !== item.id),
);
}}
style={{color: 'rgb(199,199,199)'}}
/>
}
>
<div className={'container'}>
<img alt={item.name} className={'avatar'} src={item.avatar_url}/>
<span className={'text'}>{item.name}</span>
</div>
</Badge>
</div>
))}
</Space>
<ProFormRadio.Group
name='schedule_enable'
label='绑定员工'
tooltip={'用户扫描此二维码会添加这里配置的员工'}
options={[
{
label: '全天在线',
value: Disable,
},
{
label: '自动上下线',
value: Enable,
},
]}
rules={[
{
required: true,
message: '请绑定员工',
},
]}
/>
<ProFormDependency name={['schedule_enable']}>
{({schedule_enable}) => {
// 全天在线
if (schedule_enable === Disable) {
return (
<div style={{marginLeft: 20}}>
<Space className={styles.formItem} style={{marginTop: -16, marginLeft: 12}}>
<Button
ref={staffSelectRef}
icon={<PlusOutlined/>}
onClick={() => setStaffSelectionVisible(true)}
>
添加员工
</Button>
<Text type={'secondary'}>
同一个二维码可绑定多个员工,客户扫码后随机分配一名员工进行接待
</Text>
</Space>
<Space className={styles.formItem} style={{marginTop: -16, marginLeft: 12}}>
{selectedStaffs.map((item) => (
<div key={item.id} className={'staff-item'}>
<Badge
count={
<CloseCircleOutlined
onClick={() => {
setSelectedStaffs(
selectedStaffs.filter((staff) => staff.id !== item.id),
);
}}
style={{color: 'rgb(199,199,199)'}}
/>
}
>
<div className={'container'}>
<img alt={item.name} className={'avatar'} src={item.avatar_url}/>
<span className={'text'}>{item.name}</span>
</div>
</Badge>
</div>
))}
</Space>
</div>
);
}
// 自动上下线
return (
<div className={styles.scheduleList} style={{marginLeft: 36}}>
<Alert
className={styles.tips}
type='info'
message={
'自动上下线:1、可用于员工早晚班等不同上班时间段使用;2、员工在非线上时间将不再接待新客户。'
}
/>
<ProFormList
name='schedules'
creatorButtonProps={{
type: 'default',
style: {width: '160px'},
position: 'bottom',
creatorButtonText: '添加其他工作周期',
}}
creatorRecord={{
ext_staff_ids: [],
}}
>
<ProCard className={styles.scheduleItem} ref={addScheduleRef}>
<ProForm.Item name={'id'} noStyle={true}>
<input type={'hidden'}/>
</ProForm.Item>
<Row>
<label className={styles.label}>选择员工:</label>
<ProForm.Item name={'ext_staff_ids'} style={{width: 468}}>
<StaffTreeSelect options={staffs} maxTagCount={4}/>
</ProForm.Item>
</Row>
<Row>
<label className={styles.label}>工作日:</label>
<ProFormSelect
mode='multiple'
name='weekdays'
width={468}
valueEnum={WeekdaysEnum}
placeholder='请选择工作日'
rules={[{required: true, message: '请选择工作日'}]}
/>
</Row>
<Row>
<label className={styles.label}>工作时间:</label>
<ProFormTimePicker
fieldProps={{
format: TimeLayout,
}}
name='start_time'
placeholder='开始时间'
rules={[{required: true, message: '请选择开始时间'}]}
/>
<Space style={{marginLeft: 6}}> </Space>
<ProFormTimePicker
fieldProps={{
format: TimeLayout,
}}
name='end_time'
placeholder='结束时间'
rules={[{required: true, message: '请选择结束时间'}]}
/>
</Row>
</ProCard>
</ProFormList>
</div>
);
}}
</ProFormDependency>
<ProFormSwitch
label={'员工添加上限'}
checkedChildren='开启'
unCheckedChildren='关闭'
name='daily_add_customer_limit_enable'
tooltip={'开启后员工可在侧边栏修改个人上下线状态'}
/>
<ProFormDependency name={['daily_add_customer_limit_enable']}>
{({daily_add_customer_limit_enable}) => {
if (daily_add_customer_limit_enable) {
return (
<div className={'sub-from-item'}>
<ProFormDigit
label={
<Tooltip title='员工添加上限:员工从该渠道添加的客户达到每日上限后,将自动下线当日不再接待该渠道新客户'>
每日最多添加
</Tooltip>
}
width={'md'}
name='daily_add_customer_limit'
min={0}
max={5000}
/>
</div>
);
}
return <></>;
}}
</ProFormDependency>
<ProFormSwitch
label={'员工自行上下线'}
checkedChildren='开启'
unCheckedChildren='关闭'
name='staff_control_enable'
tooltip={'开启后员工可在侧边栏修改个人上下线状态'}
/>
<ProFormSwitch
label={'客户标签:'}
checkedChildren='开启'
unCheckedChildren='关闭'
name='auto_tag_enable'
tooltip={
<>
通过此渠道码添加的客户,将自动打上此处配置的标签。您还可以转到
<Link to={'/staff-admin/customer-management/customer-tag'}> 管理客户标签</Link>
</>
}
/>
<ProFormDependency name={['auto_tag_enable']}>
{({auto_tag_enable}) => {
if (auto_tag_enable) {
return (
<div className={'sub-from-item'}>
<ProForm.Item
name={'customer_tag_ext_ids'}
label={"客户标签"}
rules={[{required: true, message: '请选择客户标签'}]}
>
<CustomerTagSelect isEditable={true} allTagGroups={tagGroups} maxTagCount={6}
style={{maxWidth: 468}}/>
</ProForm.Item>
</div>
);
}
return '';
}}
</ProFormDependency>
<ProFormSwitch
label={'客户备注:'}
checkedChildren='开启'
unCheckedChildren='关闭'
name='customer_remark_enable'
tooltip={<>客户备注将自动附加在客户昵称之后,方便员工查看</>}
/>
<ProFormDependency name={['customer_remark_enable']}>
{({customer_remark_enable}) => {
if (customer_remark_enable) {
return (
<div className={'sub-from-item'}>
<ProFormText
width={'md'}
name='customer_remark'
label='客户备注'
placeholder='客户备注'
fieldProps={{
prefix: <span style={{color: '#666666'}}>客户昵称-</span>,
}}
rules={[{required: true, message: '请填写客户备注'}]}
/>
</div>
);
}
return '';
}}
</ProFormDependency>
<ProFormSwitch
label={'客户描述:'}
checkedChildren='开启'
unCheckedChildren='关闭'
name='customer_desc_enable'
tooltip={'开启后可为客户添加描述,将在客户画像里展示'}
/>
<ProFormDependency name={['customer_desc_enable']}>
{({customer_desc_enable}) => {
if (customer_desc_enable) {
return (
<div className={'sub-from-item'}>
<ProFormText
width={'md'}
name='customer_desc'
label='客户描述'
placeholder='客户描述'
rules={[{required: true, message: '请填写客户描述'}]}
/>
</div>
);
}
return '';
}}
</ProFormDependency>
<h3 style={{marginTop: 30}}>设置欢迎语</h3>
<Divider/>
<Alert
showIcon={true}
style={{maxWidth: '680px', marginBottom: 20}}
type='info'
message={
'因企业微信限制,若使用成员已在「企业微信后台」配置了欢迎语,这里的欢迎语将不会生效'
}
/>
<ProFormRadio.Group
// 欢迎语类型:1,渠道欢迎语;2, 渠道默认欢迎语;3,不送欢迎语;
name='auto_reply_type'
label='设置欢迎语'
tooltip={'客户添加企业之后,将自动发送此消息给客户'}
options={[
{
label: '渠道欢迎语',
value: 1,
},
{
label: '渠道默认欢迎语',
value: 2,
},
{
label: '不送欢迎语',
value: 3,
},
]}
rules={[
{
required: true,
message: '请设置欢迎语',
},
]}
/>
<ProFormDependency name={['auto_reply_type']}>
{({auto_reply_type}) => {
if (auto_reply_type === 1) {
// 渠道欢迎语
return (
<div className={'sub-from-item'}>
<Form.Item
label={<span className={'form-item-required'}>欢迎语</span>}
>
<AutoReply welcomeMsg={autoReply} isFetchDone={isFetchDone} setWelcomeMsg={setAutoReply}/>
</Form.Item>
</div>
);
}
if (auto_reply_type === 2) {
// 渠道默认欢迎语
return (
<div className={'sub-from-item'} style={{marginBottom: 32, marginLeft: 26}}>
<Text type={'secondary'}>
将发送成员已设置的欢迎语,若所选成员未设置欢迎语,则不会发送欢迎语
</Text>
</div>
);
}
if (auto_reply_type === 3) {
// 不送欢迎语
return <div className={'sub-from-item'}></div>;
}
return '';
}}
</ProFormDependency>
<ProFormSwitch
label={'欢迎语屏蔽:'}
checkedChildren='开启'
unCheckedChildren='关闭'
name='nickname_block_enable'
tooltip={<>开启后,客户昵称中包含设定的关键词的客户不会收到欢迎语</>}
/>
<ProFormDependency name={['nickname_block_enable']}>
{({nickname_block_enable}) => {
if (nickname_block_enable) {
return (
<div className={'sub-from-item'} style={{marginLeft: 26, marginTop:-20}}>
<EditableTag tags={blockNicknames} setTags={setBlockNicknames}/>
</div>
);
}
return '';
}}
</ProFormDependency>
<h3 style={{marginTop: 30}}>功能设置</h3>
<Divider/>
<ProFormSwitch
label={'自动通过好友:'}
checkedChildren='开启'
unCheckedChildren='关闭'
name='skip_verify'
tooltip={<>开启后,客户添加该企业微信时,无需好友验证,将会自动添加成功</>}
/>
<ProFormDependency name={['skip_verify']}>
{({skip_verify}) => {
if (skip_verify) {
return (
<div className={'sub-from-item'}>
<ProFormRadio.Group
// 欢迎语类型:1,渠道欢迎语;2, 渠道默认欢迎语;3,不送欢迎语;
name='auto_skip_verify_enable'
label='自动通过时段'
options={[
{
label: '全天开启',
value: 2,
},
{
label: '选择时间段',
value: 1,
},
]}
rules={[
{
required: true,
message: '请选择时段控制',
},
]}
/>
<ProFormDependency name={['auto_skip_verify_enable']}>
{({auto_skip_verify_enable}) => {
if (auto_skip_verify_enable === 1) {
return (
<Row className={'sub-from-item'}>
<span style={{marginTop: 6, marginLeft: 36}}>工作时间:</span>
<ProFormTimePicker
width={'xs'}
name='skip_verify_start_time'
fieldProps={{
format: TimeLayout,
}}
placeholder='开始时间'
rules={[{required: true, message: '请选择开始时间'}]}
/>
<Space style={{marginLeft: 6}}> </Space>
<ProFormTimePicker
width={'xs'}
name='skip_verify_end_time'
fieldProps={{
format: TimeLayout,
}}
placeholder='结束时间'
rules={[{required: true, message: '请选择结束时间'}]}
/>
</Row>
);
}
return '';
}}
</ProFormDependency>
</div>
);
}
return '';
}}
</ProFormDependency>
</div>
</ProForm>
<StaffTreeSelectionModal
visible={staffSelectionVisible}
setVisible={setStaffSelectionVisible}
defaultCheckedStaffs={selectedStaffs}
onFinish={(values) => {
setSelectedStaffs(values);
}}
allStaffs={staffs}
/>
<StaffTreeSelectionModal
visible={backupStaffSelectionVisible}
setVisible={setBackupStaffSelectionVisible}
defaultCheckedStaffs={backupSelectedStaffs}
onFinish={(values: React.SetStateAction<any[]>) => {
setBackupSelectedStaffs(values);
}}
allStaffs={staffs}
/>
</>
);
}
Example #20
Source File: index.tsx From ql with MIT License | 4 votes |
export default function (props: any) {
const logout = () => {
request.post(`${config.apiPrefix}logout`).then(() => {
localStorage.removeItem(config.authKey);
history.push('/login');
});
};
useEffect(() => {
const isAuth = localStorage.getItem(config.authKey);
if (!isAuth) {
history.push('/login');
}
vhCheck();
}, []);
useEffect(() => {
if (props.location.pathname === '/') {
history.push('/crontab');
}
}, [props.location.pathname]);
useEffect(() => {
const theme = localStorage.getItem('qinglong_dark_theme') || 'auto';
setFetchMethod(window.fetch);
if (theme === 'dark') {
enableDarkMode({
brightness: 100,
contrast: 90,
sepia: 10,
});
} else if (theme === 'light') {
disableDarkMode();
} else {
followSystemColorScheme({
brightness: 100,
contrast: 90,
sepia: 10,
});
}
}, []);
if (props.location.pathname === '/login') {
return props.children;
}
const isFirefox = navigator.userAgent.includes('Firefox');
const isSafari =
navigator.userAgent.includes('Safari') &&
!navigator.userAgent.includes('Chrome');
return (
<ProLayout
selectedKeys={[props.location.pathname]}
title={
<>
控制面板
<a href={changeLog} target="_blank" rel="noopener noreferrer">
<span
style={{
fontSize: isFirefox ? 9 : 12,
color: '#666',
marginLeft: 5,
zoom: isSafari ? 0.66 : 0.8,
}}
>
{version}
</span>
</a>
</>
}
menuItemRender={(menuItemProps: any, defaultDom: any) => {
if (
menuItemProps.isUrl ||
!menuItemProps.path ||
location.pathname === menuItemProps.path
) {
return defaultDom;
}
return <Link to={menuItemProps.path}>{defaultDom}</Link>;
}}
postMenuData={(menuData) => {
return [
...(menuData || []),
{
icon: <LogoutOutlined />,
name: '退出登录',
path: 'logout',
onTitleClick: () => logout(),
},
];
}}
pageTitleRender={() => '控制面板'}
{...defaultProps}
>
{props.children}
</ProLayout>
);
}
Example #21
Source File: index.tsx From ant-design-pro-V5-multitab with MIT License | 4 votes |
Login: React.FC<{}> = () => {
const [submitting, setSubmitting] = useState(false);
const [userLoginState, setUserLoginState] = useState<API.LoginStateType>({});
const [type, setType] = useState<string>('account');
const { initialState, setInitialState } = useModel('@@initialState');
const intl = useIntl();
const fetchUserInfo = async () => {
const userInfo = await initialState?.fetchUserInfo?.();
if (userInfo) {
setInitialState({
...initialState,
currentUser: userInfo,
});
}
};
const handleSubmit = async (values: LoginParamsType) => {
setSubmitting(true);
try {
// 登录
const msg = await fakeAccountLogin({ ...values, type });
if (msg.status === 'ok') {
message.success('登录成功!');
await fetchUserInfo();
goto();
return;
}
// 如果失败去设置用户错误信息
setUserLoginState(msg);
} catch (error) {
message.error('登录失败,请重试!');
}
setSubmitting(false);
};
const { status, type: loginType } = userLoginState;
return (
<div className={styles.container}>
<div className={styles.lang}>{SelectLang && <SelectLang />}</div>
<div className={styles.content}>
<div className={styles.top}>
<div className={styles.header}>
<Link to="/">
<img alt="logo" className={styles.logo} src="/logo.svg" />
<span className={styles.title}>Ant Design</span>
</Link>
</div>
<div className={styles.desc}>Ant Design 是西湖区最具影响力的 Web 设计规范</div>
</div>
<div className={styles.main}>
<ProForm
initialValues={{
autoLogin: true,
}}
submitter={{
searchConfig: {
submitText: intl.formatMessage({
id: 'pages.login.submit',
defaultMessage: '登录',
}),
},
render: (_, dom) => dom.pop(),
submitButtonProps: {
loading: submitting,
size: 'large',
style: {
width: '100%',
},
},
}}
onFinish={async (values) => {
handleSubmit(values);
}}
>
<Tabs activeKey={type} onChange={setType}>
<Tabs.TabPane
key="account"
tab={intl.formatMessage({
id: 'pages.login.accountLogin.tab',
defaultMessage: '账户密码登录',
})}
/>
<Tabs.TabPane
key="mobile"
tab={intl.formatMessage({
id: 'pages.login.phoneLogin.tab',
defaultMessage: '手机号登录',
})}
/>
</Tabs>
{status === 'error' && loginType === 'account' && (
<LoginMessage
content={intl.formatMessage({
id: 'pages.login.accountLogin.errorMessage',
defaultMessage: '账户或密码错误(admin/ant.design)',
})}
/>
)}
{type === 'account' && (
<>
<ProFormText
name="username"
fieldProps={{
size: 'large',
prefix: <UserOutlined className={styles.prefixIcon} />,
}}
placeholder={intl.formatMessage({
id: 'pages.login.username.placeholder',
defaultMessage: '用户名: admin or user',
})}
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.login.username.required"
defaultMessage="请输入用户名!"
/>
),
},
]}
/>
<ProFormText.Password
name="password"
fieldProps={{
size: 'large',
prefix: <LockTwoTone className={styles.prefixIcon} />,
}}
placeholder={intl.formatMessage({
id: 'pages.login.password.placeholder',
defaultMessage: '密码: ant.design',
})}
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.login.password.required"
defaultMessage="请输入密码!"
/>
),
},
]}
/>
</>
)}
{status === 'error' && loginType === 'mobile' && <LoginMessage content="验证码错误" />}
{type === 'mobile' && (
<>
<ProFormText
fieldProps={{
size: 'large',
prefix: <MobileTwoTone className={styles.prefixIcon} />,
}}
name="mobile"
placeholder={intl.formatMessage({
id: 'pages.login.phoneNumber.placeholder',
defaultMessage: '手机号',
})}
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.login.phoneNumber.required"
defaultMessage="请输入手机号!"
/>
),
},
{
pattern: /^1\d{10}$/,
message: (
<FormattedMessage
id="pages.login.phoneNumber.invalid"
defaultMessage="手机号格式错误!"
/>
),
},
]}
/>
<ProFormCaptcha
fieldProps={{
size: 'large',
prefix: <MailTwoTone className={styles.prefixIcon} />,
}}
captchaProps={{
size: 'large',
}}
placeholder={intl.formatMessage({
id: 'pages.login.captcha.placeholder',
defaultMessage: '请输入验证码',
})}
captchaTextRender={(timing, count) =>
timing
? `${count} ${intl.formatMessage({
id: 'pages.getCaptchaSecondText',
defaultMessage: '获取验证码',
})}`
: intl.formatMessage({
id: 'pages.login.phoneLogin.getVerificationCode',
defaultMessage: '获取验证码',
})
}
name="captcha"
rules={[
{
required: true,
message: (
<FormattedMessage
id="pages.login.captcha.required"
defaultMessage="请输入验证码!"
/>
),
},
]}
onGetCaptcha={async (mobile) => {
const result = await getFakeCaptcha(mobile);
if (result === false) {
return;
}
message.success('获取验证码成功!验证码为:1234');
}}
/>
</>
)}
<div
style={{
marginBottom: 24,
}}
>
<ProFormCheckbox noStyle name="autoLogin">
<FormattedMessage id="pages.login.rememberMe" defaultMessage="自动登录" />
</ProFormCheckbox>
<a
style={{
float: 'right',
}}
>
<FormattedMessage id="pages.login.forgotPassword" defaultMessage="忘记密码" />
</a>
</div>
</ProForm>
<Space className={styles.other}>
<FormattedMessage id="pages.login.loginWith" defaultMessage="其他登录方式" />
<AlipayCircleOutlined className={styles.icon} />
<TaobaoCircleOutlined className={styles.icon} />
<WeiboCircleOutlined className={styles.icon} />
</Space>
</div>
</div>
<Footer />
</div>
);
}
Example #22
Source File: index.tsx From anew-server with MIT License | 4 votes |
Login: React.FC = () => {
const [submitting, setSubmitting] = useState(false);
const { initialState, setInitialState } = useModel('@@initialState');
const fetchUserInfo = async () => {
const userInfo = await initialState?.fetchUserInfo?.();
if (userInfo) {
setInitialState({ ...initialState, currentUser: userInfo });
}
};
const handleSubmit = async (values: API.LoginParams) => {
setSubmitting(true);
try {
// 登录
const login = await AuthLogin(values);
if (login.status) {
const defaultloginSuccessMessage = '登录成功';
message.success(defaultloginSuccessMessage);
localStorage.setItem('token', login.data.token);
localStorage.setItem('expires', login.data.expires);
await fetchUserInfo();
goto();
return;
} // 如果失败去设置用户错误信息
} catch (error) {
// 全局状态已处理,这里返回
return
}
setSubmitting(false);
};
return (
<div className={styles.container}>
<div className={styles.content}>
<div className={styles.top}>
<div className={styles.header}>
<Link to="/">
<img alt="logo" className={styles.logo} src="/logo.svg" />
<span className={styles.title}>Anew-Server</span>
</Link>
</div>
<div className={styles.desc}>{'运维平台'}</div>
</div>
<div className={styles.main}>
<ProForm
initialValues={{
autoLogin: true,
}}
submitter={{
searchConfig: {
submitText: '登录',
},
render: (_, dom) => dom.pop(),
submitButtonProps: {
loading: submitting,
size: 'large',
style: {
width: '100%',
},
},
}}
onFinish={async (values) => {
handleSubmit(values as API.LoginParams);
}}
>
<>
<ProFormText
name="username"
fieldProps={{
size: 'large',
prefix: <UserOutlined className={styles.prefixIcon} />,
}}
placeholder={'用户名: admin or user'}
rules={[
{
required: true,
message: '用户名是必填项!',
},
]}
/>
<ProFormText.Password
name="password"
fieldProps={{
size: 'large',
prefix: <LockOutlined className={styles.prefixIcon} />,
}}
placeholder={'密码: ant.design'}
rules={[
{
required: true,
message: '密码是必填项!',
},
]}
/>
</>
</ProForm>
</div>
</div>
<Footer />
</div>
);
}