mirror of
https://github.com/zuiidea/antd-admin
synced 2026-10-30 13:23:49 +00:00
✨ adding login page.
This commit is contained in:
+1
-1
@@ -56,7 +56,7 @@
|
||||
"eslint-plugin-prettier": "^3.3.1",
|
||||
"eslint-plugin-react": "^7.23.1",
|
||||
"eslint-plugin-react-hooks": "^4.2.0",
|
||||
"husky": "^4.2.0",
|
||||
"husky": "^6.0.0",
|
||||
"less-vars-to-js": "^1.3.0",
|
||||
"lint-staged": "^10.0.0",
|
||||
"mockjs": "^1.1.0",
|
||||
|
||||
-14
@@ -1,14 +0,0 @@
|
||||
import React from 'react';
|
||||
export interface GlobalFooterProps {
|
||||
links?: Array<{
|
||||
key?: string;
|
||||
title: React.ReactNode;
|
||||
href: string;
|
||||
blankTarget?: boolean;
|
||||
}>;
|
||||
copyright?: React.ReactNode;
|
||||
style?: React.CSSProperties;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export default class GlobalFooter extends React.Component<GlobalFooterProps, any> {}
|
||||
@@ -1,28 +0,0 @@
|
||||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import styles from './index.less';
|
||||
|
||||
const GlobalFooter = ({ className, links, copyright }) => {
|
||||
const clsString = classNames(styles.globalFooter, className);
|
||||
return (
|
||||
<footer className={clsString}>
|
||||
{links && (
|
||||
<div className={styles.links}>
|
||||
{links.map(link => (
|
||||
<a
|
||||
key={link.key}
|
||||
title={link.key}
|
||||
target={link.blankTarget ? '_blank' : '_self'}
|
||||
href={link.href}
|
||||
>
|
||||
{link.title}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{copyright && <div className={styles.copyright}>{copyright}</div>}
|
||||
</footer>
|
||||
);
|
||||
};
|
||||
|
||||
export default GlobalFooter;
|
||||
@@ -13,7 +13,7 @@
|
||||
transition: all 0.3s;
|
||||
|
||||
&:not(:last-child) {
|
||||
margin-right: 40px;
|
||||
margin-right: 16px;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
title: GlobalFooter
|
||||
subtitle: 全局页脚
|
||||
cols: 1
|
||||
order: 7
|
||||
---
|
||||
|
||||
页脚属于全局导航的一部分,作为对顶部导航的补充,通过传递数据控制展示内容。
|
||||
|
||||
## API
|
||||
|
||||
| 参数 | 说明 | 类型 | 默认值 |
|
||||
| --------- | -------- | ---------------------------------------------------------------- | ------ |
|
||||
| links | 链接数据 | array<{ title: ReactNode, href: string, blankTarget?: boolean }> | - |
|
||||
| copyright | 版权信息 | ReactNode | - |
|
||||
@@ -0,0 +1,39 @@
|
||||
import React from 'react'
|
||||
import styles from './index.less'
|
||||
|
||||
interface LinkItem {
|
||||
key: string
|
||||
title: React.ReactNode
|
||||
href?: string
|
||||
blankTarget?: boolean
|
||||
}
|
||||
|
||||
interface GlobalFooterProps {
|
||||
links?: LinkItem[]
|
||||
copyright?: React.ReactNode
|
||||
}
|
||||
|
||||
const GlobalFooter: React.FC<GlobalFooterProps> = ({
|
||||
links = [],
|
||||
copyright,
|
||||
}) => {
|
||||
return (
|
||||
<footer className={styles.globalFooter}>
|
||||
<div className={styles.links}>
|
||||
{links.map((link) => (
|
||||
<a
|
||||
key={link.key}
|
||||
title={link.key}
|
||||
target={link.blankTarget ? '_blank' : '_self'}
|
||||
href={link.href}
|
||||
>
|
||||
{link.title}
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
{copyright && <div className={styles.copyright}>{copyright}</div>}
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
|
||||
export default GlobalFooter
|
||||
@@ -1,20 +1,12 @@
|
||||
import React, { PureComponent, Fragment } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
<<<<<<< HEAD
|
||||
import { Breadcrumb, Icon } from 'antd'
|
||||
import { Link } from 'umi'
|
||||
import withRouter from 'umi/withRouter'
|
||||
import { withI18n } from '@lingui/react'
|
||||
import { pathMatchRegexp, queryAncestors } from 'utils'
|
||||
=======
|
||||
import { Breadcrumb } from 'antd'
|
||||
import { Link, withRouter } from 'umi'
|
||||
import { t } from "@lingui/macro"
|
||||
import { t } from '@lingui/macro'
|
||||
import iconMap from 'utils/iconMap'
|
||||
const { pathToRegexp } = require('path-to-regexp')
|
||||
import { queryAncestors } from 'utils'
|
||||
>>>>>>> master
|
||||
import styles from './Bread.less'
|
||||
const { pathToRegexp } = require('path-to-regexp')
|
||||
|
||||
@withRouter
|
||||
class Bread extends PureComponent {
|
||||
|
||||
@@ -1,22 +1,10 @@
|
||||
import React, { PureComponent, Fragment } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
<<<<<<< HEAD
|
||||
import { Menu, Icon } from 'antd'
|
||||
import { Link, Navlink } from 'umi'
|
||||
import withRouter from 'umi/withRouter'
|
||||
import {
|
||||
arrayToTree,
|
||||
queryAncestors,
|
||||
pathMatchRegexp,
|
||||
addLangPrefix,
|
||||
} from 'utils'
|
||||
=======
|
||||
import { Menu } from 'antd'
|
||||
import { NavLink, withRouter } from 'umi'
|
||||
import { pathToRegexp } from 'path-to-regexp'
|
||||
import { arrayToTree, queryAncestors } from 'utils'
|
||||
import iconMap from 'utils/iconMap'
|
||||
>>>>>>> master
|
||||
import store from 'store'
|
||||
|
||||
const { SubMenu } = Menu
|
||||
@@ -27,12 +15,14 @@ class SiderMenu extends PureComponent {
|
||||
openKeys: store.get('openKeys') || [],
|
||||
}
|
||||
|
||||
onOpenChange = openKeys => {
|
||||
onOpenChange = (openKeys) => {
|
||||
const { menus } = this.props
|
||||
const rootSubmenuKeys = menus.filter(_ => !_.menuParentId).map(_ => _.id)
|
||||
const rootSubmenuKeys = menus
|
||||
.filter((_) => !_.menuParentId)
|
||||
.map((_) => _.id)
|
||||
|
||||
const latestOpenKey = openKeys.find(
|
||||
key => this.state.openKeys.indexOf(key) === -1
|
||||
(key) => this.state.openKeys.indexOf(key) === -1
|
||||
)
|
||||
|
||||
let newOpenKeys = openKeys
|
||||
@@ -46,8 +36,8 @@ class SiderMenu extends PureComponent {
|
||||
store.set('openKeys', newOpenKeys)
|
||||
}
|
||||
|
||||
generateMenus = data => {
|
||||
return data.map(item => {
|
||||
generateMenus = (data) => {
|
||||
return data.map((item) => {
|
||||
if (item.children) {
|
||||
return (
|
||||
<SubMenu
|
||||
@@ -89,12 +79,12 @@ class SiderMenu extends PureComponent {
|
||||
|
||||
// Find a menu that matches the pathname.
|
||||
const currentMenu = menus.find(
|
||||
_ => _.route && pathToRegexp(_.route).exec(location.pathname)
|
||||
(_) => _.route && pathToRegexp(_.route).exec(location.pathname)
|
||||
)
|
||||
|
||||
// Find the key that should be selected according to the current menu.
|
||||
const selectedKeys = currentMenu
|
||||
? queryAncestors(menus, currentMenu, 'menuParentId').map(_ => _.id)
|
||||
? queryAncestors(menus, currentMenu, 'menuParentId').map((_) => _.id)
|
||||
: []
|
||||
|
||||
const menuProps = collapsed
|
||||
|
||||
+1
-12
@@ -4,16 +4,5 @@ import Loader from './Loader'
|
||||
import ScrollBar from './ScrollBar'
|
||||
import GlobalFooter from './GlobalFooter'
|
||||
import Ellipsis from './Ellipsis'
|
||||
import * as MyLayout from './Layout/index.js'
|
||||
import Page from './Page'
|
||||
|
||||
export {
|
||||
MyLayout,
|
||||
GlobalFooter,
|
||||
Ellipsis,
|
||||
FilterItem,
|
||||
DropOption,
|
||||
Loader,
|
||||
Page,
|
||||
ScrollBar,
|
||||
}
|
||||
export { GlobalFooter, Ellipsis, FilterItem, DropOption, Loader, ScrollBar }
|
||||
|
||||
@@ -12,13 +12,13 @@ const language = detect(
|
||||
checkLanguage(fromUrl('lang')),
|
||||
checkLanguage(fromStorage(LOCALE_LANGUAGE)),
|
||||
checkLanguage(fromNavigator()),
|
||||
'zh'
|
||||
'en'
|
||||
)
|
||||
|
||||
export const config: IConfig = {
|
||||
title: 'React Seed',
|
||||
title: 'Antd Admin',
|
||||
logo: 'logo.svg',
|
||||
copyright: 'Ant Design Admin © 2018 zuiidea',
|
||||
copyright: 'Ant Design Admin © 2021 zuiidea',
|
||||
language: language as ISupportedLocales,
|
||||
|
||||
/* Layout configuration, specify which layout to use for route. */
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { ILanguage, IAntdI18nMap } from '@/typings'
|
||||
import { ILanguage, ILanguages, IAntdI18nMap } from '@/typings'
|
||||
|
||||
/**
|
||||
* Countrys flags: https://www.flaticon.com/packs/countrys-flags
|
||||
*/
|
||||
export const language: ILanguage = {
|
||||
en: {
|
||||
flag: '/america.svg',
|
||||
@@ -9,14 +12,21 @@ export const language: ILanguage = {
|
||||
flag: '/china.svg',
|
||||
value: '简体中文',
|
||||
},
|
||||
'pt-br': {
|
||||
flag: '/portugal.svg',
|
||||
value: 'Português',
|
||||
},
|
||||
}
|
||||
|
||||
export const languages = Object.entries(language).map(([key, value]: any) => ({
|
||||
key,
|
||||
...value,
|
||||
}))
|
||||
export const languages: ILanguages[] = Object.entries(language).map(
|
||||
([key, value]: any) => ({
|
||||
key,
|
||||
...value,
|
||||
})
|
||||
)
|
||||
|
||||
export const antdI18nMap: IAntdI18nMap = {
|
||||
zh: 'zh_CN',
|
||||
en: 'en_US',
|
||||
'pt-br': 'pt_PT',
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"POT-Creation-Date: 2021-04-05 15:59+0800\n"
|
||||
"Mime-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=utf-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Generator: @lingui/cli\n"
|
||||
"Language: pt-br\n"
|
||||
|
||||
#: src/pages/login/index.tsx:14
|
||||
msgid "Sign in"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/login/index.tsx:18
|
||||
msgid "Username"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/login/index.tsx:22
|
||||
msgid "Password"
|
||||
msgstr ""
|
||||
@@ -0,0 +1,5 @@
|
||||
import { Redirect } from 'umi'
|
||||
|
||||
const Index = () => <Redirect to="/dashboard" />
|
||||
|
||||
export default Index
|
||||
+15
-15
@@ -4,9 +4,9 @@
|
||||
position: absolute;
|
||||
top: 45%;
|
||||
left: 50%;
|
||||
margin: -160px 0 0 -160px;
|
||||
width: 320px;
|
||||
height: 320px;
|
||||
margin: -160px 0 0 -160px;
|
||||
padding: 36px;
|
||||
box-shadow: 0 0 100px rgba(0, 0, 0, 0.08);
|
||||
|
||||
@@ -15,22 +15,22 @@
|
||||
}
|
||||
|
||||
p {
|
||||
color: rgb(204, 204, 204);
|
||||
text-align: center;
|
||||
margin-top: 16px;
|
||||
font-size: 12px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-top: 16px;
|
||||
color: rgb(204, 204, 204);
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 24px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
margin-bottom: 24px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
img {
|
||||
width: 40px;
|
||||
@@ -38,13 +38,13 @@
|
||||
}
|
||||
|
||||
span {
|
||||
display: inline-block;
|
||||
color: @primary-color;
|
||||
font-weight: 700;
|
||||
vertical-align: text-bottom;
|
||||
font-size: 16px;
|
||||
text-transform: uppercase;
|
||||
vertical-align: text-bottom;
|
||||
@text-gradient .text-gradient();
|
||||
display: inline-block;
|
||||
font-weight: 700;
|
||||
color: @primary-color;
|
||||
.text-gradient();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,6 +55,6 @@
|
||||
|
||||
.footer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
+46
-10
@@ -1,26 +1,59 @@
|
||||
import React from 'react'
|
||||
import { Button, Row } from 'antd'
|
||||
import { Trans } from '@lingui/macro'
|
||||
import { config } from '@/configs'
|
||||
import { Button, Row, Input, Form } from 'antd'
|
||||
import { GithubOutlined } from '@ant-design/icons'
|
||||
import { GlobalFooter } from '@/components'
|
||||
import { Trans, t } from '@lingui/macro'
|
||||
import { config, languages } from '@/configs'
|
||||
import styles from './index.less'
|
||||
|
||||
const FormItem = Form.Item
|
||||
|
||||
const Login: React.FC = () => {
|
||||
const footerLinks = [
|
||||
{
|
||||
key: 'github',
|
||||
title: <GithubOutlined />,
|
||||
href: 'https://github.com/zuiidea/antd-admin',
|
||||
blankTarget: true,
|
||||
},
|
||||
...languages.map((item) => ({
|
||||
key: item.key,
|
||||
title: <span>{item.value}</span>,
|
||||
})),
|
||||
]
|
||||
|
||||
const handleFinish = (values) => {
|
||||
console.log(values)
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.form}>
|
||||
<div className={styles.logo}>
|
||||
<img alt="logo" src={config.logo} />
|
||||
<span>{config.title}</span>
|
||||
</div>
|
||||
<Form onFinish={handleFinish}>
|
||||
<FormItem
|
||||
name="username"
|
||||
rules={[{ required: true, message: 'Please input your username!' }]}
|
||||
hasFeedback
|
||||
>
|
||||
<Input placeholder={t`Username`} />
|
||||
</FormItem>
|
||||
<FormItem
|
||||
name="password"
|
||||
rules={[{ required: true, message: 'Please input your password!' }]}
|
||||
hasFeedback
|
||||
>
|
||||
<Input.Password placeholder={t`Password`} />
|
||||
</FormItem>
|
||||
<Row>
|
||||
<Button
|
||||
type="primary"
|
||||
// onClick={this.handleOk}
|
||||
// loading={loading.effects.login}
|
||||
>
|
||||
<Button type="primary" htmlType="submit">
|
||||
<Trans>Sign in</Trans>
|
||||
</Button>
|
||||
<p>
|
||||
<span>
|
||||
<span className="margin-right">
|
||||
<Trans>Username</Trans>
|
||||
:guest
|
||||
</span>
|
||||
@@ -30,7 +63,10 @@ const Login: React.FC = () => {
|
||||
</span>
|
||||
</p>
|
||||
</Row>
|
||||
</div>
|
||||
</Form>
|
||||
</div>
|
||||
<div className={styles.footer}>
|
||||
<GlobalFooter links={footerLinks} copyright={config.copyright} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -14,6 +14,12 @@ export interface ILanguage {
|
||||
[key: string]: ILanguageItem
|
||||
}
|
||||
|
||||
export interface ILanguages {
|
||||
key: string
|
||||
flag: string
|
||||
value: string
|
||||
}
|
||||
|
||||
export interface IAntdI18nMap {
|
||||
[key: string]: string
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user