25 changed files with 649 additions and 57 deletions
@ -0,0 +1,103 @@ |
|||
Русский | [English](./README.md) | [简体中文](./README.zh-CN.md) |
|||
|
|||
# Ant Design Pro |
|||
|
|||
[](https://travis-ci.org/ant-design/ant-design-pro) |
|||
[](https://ci.appveyor.com/project/afc163/ant-design-pro/branch/master) |
|||
[](https://david-dm.org/ant-design/ant-design-pro) |
|||
[](https://david-dm.org/ant-design/ant-design-pro#info=devDependencies&view=list) |
|||
[](https://gitter.im/ant-design/ant-design-pro?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) |
|||
|
|||
UI-решение "из коробки" для корпоративных приложений как React boilerplate |
|||
|
|||
 |
|||
|
|||
- Демо: http://preview.pro.ant.design |
|||
- Домашняя страница: http://pro.ant.design |
|||
- Документация: http://pro.ant.design/docs/getting-started |
|||
- История изменений: http://pro.ant.design/docs/changelog |
|||
- FAQ: http://pro.ant.design/docs/faq |
|||
- Китайское зеркало сайта: http://ant-design-pro.gitee.io |
|||
|
|||
## Поиск переводчиков :loudspeaker: |
|||
|
|||
Нам нужна ваша помощь: https://github.com/ant-design/ant-design-pro/issues/120 |
|||
|
|||
## Возможности |
|||
|
|||
- :gem: **Аккуратный дизайн**: Посмотрите [спецификацию Ant Design](http://ant.design/) |
|||
- :triangular_ruler: **Общие шаблоны**: Стандартные шаблоны для корпоративных приложений |
|||
- :rocket: **Разработка, как искусство**: Новейший стек технологий React/dva/antd |
|||
- :iphone: **Отзывчивая верстка**: Создан для экранов разных размеров |
|||
- :art: **Темизация**: Возможность изменения темы с помощью конфигурации |
|||
- :globe_with_meridians: **Мультиязычность**: Встроенное i18n решение |
|||
- :gear: **Лучшие практики**: Надежные процессы для хорошего кода |
|||
- :1234: **Разработка по шиблону**: Простое в использовании решение для разработки |
|||
- :white_check_mark: **UI тесты**: Разрабатывайте безопасно с юнит и e2e тестами |
|||
|
|||
## Шаблоны |
|||
|
|||
``` |
|||
- Dashboard |
|||
- Analytic |
|||
- Monitor |
|||
- Workspace |
|||
- Form |
|||
- Basic Form |
|||
- Step Form |
|||
- Advanced From |
|||
- List |
|||
- Standard Table |
|||
- Standard List |
|||
- Card List |
|||
- Search List (Project/Applications/Article) |
|||
- Profile |
|||
- Simple Profile |
|||
- Advanced Profile |
|||
- Result |
|||
- Success |
|||
- Failed |
|||
- Exception |
|||
- 403 |
|||
- 404 |
|||
- 500 |
|||
- User |
|||
- Login |
|||
- Register |
|||
- Register Result |
|||
``` |
|||
|
|||
## Использование |
|||
|
|||
```bash |
|||
$ git clone https://github.com/ant-design/ant-design-pro.git --depth=1 |
|||
$ cd ant-design-pro |
|||
$ npm install |
|||
$ npm start # visit http://localhost:8000 |
|||
``` |
|||
|
|||
Также можно использовать инструмент командной строки: [ant-design-pro-cli](https://github.com/ant-design/ant-design-pro-cli) |
|||
|
|||
```bash |
|||
$ npm install ant-design-pro-cli -g |
|||
$ mkdir pro-demo && cd pro-demo |
|||
$ pro new |
|||
``` |
|||
|
|||
Больше информации в [документации](http://pro.ant.design/docs/getting-started). |
|||
|
|||
## Совместимость |
|||
|
|||
Современные браузеры и IE11. |
|||
|
|||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt="IE / Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE / Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/opera/opera_48x48.png" alt="Opera" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Opera | |
|||
| --------- | --------- | --------- | --------- | --------- | |
|||
| IE11, Edge| last 2 versions| last 2 versions| last 2 versions| last 2 versions |
|||
|
|||
## Распространение |
|||
|
|||
Любые варианты распространения приветствуются! Вот несколько примероы того, как вы можете помочь распространению проекта: |
|||
|
|||
- Использовать Ant Design Pro в ежедневной работе. |
|||
- Создавать [задачи](http://github.com/ant-design/ant-design-pro/issues) заводить баги или отвечать на вопросы. |
|||
- Делать [pull-реквесты](http://github.com/ant-design/ant-design-pro/pulls) для совершенствования нашего кода. |
|||
@ -0,0 +1,3 @@ |
|||
module.exports = { |
|||
testURL: 'http://localhost:8000', |
|||
}; |
|||
@ -0,0 +1,309 @@ |
|||
import React, { Fragment } from 'react'; |
|||
import PropTypes from 'prop-types'; |
|||
import { Layout, Icon, message } from 'antd'; |
|||
import DocumentTitle from 'react-document-title'; |
|||
import { connect } from 'dva'; |
|||
import { Route, Redirect, Switch, routerRedux } from 'dva/router'; |
|||
import { ContainerQuery } from 'react-container-query'; |
|||
import classNames from 'classnames'; |
|||
import pathToRegexp from 'path-to-regexp'; |
|||
import { enquireScreen, unenquireScreen } from 'enquire-js'; |
|||
import GlobalHeader from '../components/GlobalHeader'; |
|||
import GlobalFooter from '../components/GlobalFooter'; |
|||
import SiderMenu from '../components/SiderMenu'; |
|||
import NotFound from '../routes/Exception/404'; |
|||
import { getRoutes } from '../utils/utils'; |
|||
import Authorized from '../utils/Authorized'; |
|||
import { getMenuData } from '../common/menu'; |
|||
import logo from '../assets/logo.svg'; |
|||
|
|||
const { Content, Header, Footer } = Layout; |
|||
const { AuthorizedRoute, check } = Authorized; |
|||
|
|||
/** |
|||
* 根据菜单取得重定向地址. |
|||
*/ |
|||
const redirectData = []; |
|||
const getRedirect = item => { |
|||
if (item && item.children) { |
|||
if (item.children[0] && item.children[0].path) { |
|||
redirectData.push({ |
|||
from: `${item.path}`, |
|||
to: `${item.children[0].path}`, |
|||
}); |
|||
item.children.forEach(children => { |
|||
getRedirect(children); |
|||
}); |
|||
} |
|||
} |
|||
}; |
|||
getMenuData().forEach(getRedirect); |
|||
|
|||
/** |
|||
* 获取面包屑映射 |
|||
* @param {Object} menuData 菜单配置 |
|||
* @param {Object} routerData 路由配置 |
|||
*/ |
|||
const getBreadcrumbNameMap = (menuData, routerData) => { |
|||
const result = {}; |
|||
const childResult = {}; |
|||
for (const i of menuData) { |
|||
if (!routerData[i.path]) { |
|||
result[i.path] = i; |
|||
} |
|||
if (i.children) { |
|||
Object.assign(childResult, getBreadcrumbNameMap(i.children, routerData)); |
|||
} |
|||
} |
|||
return Object.assign({}, routerData, result, childResult); |
|||
}; |
|||
|
|||
const query = { |
|||
'screen-xs': { |
|||
maxWidth: 575, |
|||
}, |
|||
'screen-sm': { |
|||
minWidth: 576, |
|||
maxWidth: 767, |
|||
}, |
|||
'screen-md': { |
|||
minWidth: 768, |
|||
maxWidth: 991, |
|||
}, |
|||
'screen-lg': { |
|||
minWidth: 992, |
|||
maxWidth: 1199, |
|||
}, |
|||
'screen-xl': { |
|||
minWidth: 1200, |
|||
maxWidth: 1599, |
|||
}, |
|||
'screen-xxl': { |
|||
minWidth: 1600, |
|||
}, |
|||
}; |
|||
|
|||
let isMobile; |
|||
enquireScreen(b => { |
|||
isMobile = b; |
|||
}); |
|||
|
|||
@connect(({ user, global = {}, loading }) => ({ |
|||
currentUser: user.currentUser, |
|||
collapsed: global.collapsed, |
|||
fetchingNotices: loading.effects['global/fetchNotices'], |
|||
notices: global.notices, |
|||
})) |
|||
export default class BasicLayout extends React.PureComponent { |
|||
static childContextTypes = { |
|||
location: PropTypes.object, |
|||
breadcrumbNameMap: PropTypes.object, |
|||
}; |
|||
|
|||
state = { |
|||
isMobile, |
|||
}; |
|||
|
|||
getChildContext() { |
|||
const { location, routerData } = this.props; |
|||
return { |
|||
location, |
|||
breadcrumbNameMap: getBreadcrumbNameMap(getMenuData(), routerData), |
|||
}; |
|||
} |
|||
|
|||
componentDidMount() { |
|||
this.enquireHandler = enquireScreen(mobile => { |
|||
this.setState({ |
|||
isMobile: mobile, |
|||
}); |
|||
}); |
|||
const { dispatch } = this.props; |
|||
dispatch({ |
|||
type: 'user/fetchCurrent', |
|||
}); |
|||
} |
|||
|
|||
componentWillUnmount() { |
|||
unenquireScreen(this.enquireHandler); |
|||
} |
|||
|
|||
getPageTitle() { |
|||
const { routerData, location } = this.props; |
|||
const { pathname } = location; |
|||
let title = 'Ant Design Pro'; |
|||
let currRouterData = null; |
|||
// match params path
|
|||
Object.keys(routerData).forEach(key => { |
|||
if (pathToRegexp(key).test(pathname)) { |
|||
currRouterData = routerData[key]; |
|||
} |
|||
}); |
|||
if (currRouterData && currRouterData.name) { |
|||
title = `${currRouterData.name} - Ant Design Pro`; |
|||
} |
|||
return title; |
|||
} |
|||
|
|||
getBaseRedirect = () => { |
|||
// According to the url parameter to redirect
|
|||
// 这里是重定向的,重定向到 url 的 redirect 参数所示地址
|
|||
const urlParams = new URL(window.location.href); |
|||
|
|||
const redirect = urlParams.searchParams.get('redirect'); |
|||
// Remove the parameters in the url
|
|||
if (redirect) { |
|||
urlParams.searchParams.delete('redirect'); |
|||
window.history.replaceState(null, 'redirect', urlParams.href); |
|||
} else { |
|||
const { routerData } = this.props; |
|||
// get the first authorized route path in routerData
|
|||
const authorizedPath = Object.keys(routerData).find( |
|||
item => check(routerData[item].authority, item) && item !== '/' |
|||
); |
|||
return authorizedPath; |
|||
} |
|||
return redirect; |
|||
}; |
|||
|
|||
handleMenuCollapse = collapsed => { |
|||
const { dispatch } = this.props; |
|||
dispatch({ |
|||
type: 'global/changeLayoutCollapsed', |
|||
payload: collapsed, |
|||
}); |
|||
}; |
|||
|
|||
handleNoticeClear = type => { |
|||
message.success(`清空了${type}`); |
|||
const { dispatch } = this.props; |
|||
dispatch({ |
|||
type: 'global/clearNotices', |
|||
payload: type, |
|||
}); |
|||
}; |
|||
|
|||
handleMenuClick = ({ key }) => { |
|||
const { dispatch } = this.props; |
|||
if (key === 'triggerError') { |
|||
dispatch(routerRedux.push('/exception/trigger')); |
|||
return; |
|||
} |
|||
if (key === 'logout') { |
|||
dispatch({ |
|||
type: 'login/logout', |
|||
}); |
|||
} |
|||
}; |
|||
|
|||
handleNoticeVisibleChange = visible => { |
|||
const { dispatch } = this.props; |
|||
if (visible) { |
|||
dispatch({ |
|||
type: 'global/fetchNotices', |
|||
}); |
|||
} |
|||
}; |
|||
|
|||
render() { |
|||
const { |
|||
currentUser, |
|||
collapsed, |
|||
fetchingNotices, |
|||
notices, |
|||
routerData, |
|||
match, |
|||
location, |
|||
} = this.props; |
|||
const { isMobile: mb } = this.state; |
|||
const baseRedirect = this.getBaseRedirect(); |
|||
const layout = ( |
|||
<Layout> |
|||
<SiderMenu |
|||
logo={logo} |
|||
// 不带Authorized参数的情况下如果没有权限,会强制跳到403界面
|
|||
// If you do not have the Authorized parameter
|
|||
// you will be forced to jump to the 403 interface without permission
|
|||
Authorized={Authorized} |
|||
menuData={getMenuData()} |
|||
collapsed={collapsed} |
|||
location={location} |
|||
isMobile={mb} |
|||
onCollapse={this.handleMenuCollapse} |
|||
/> |
|||
<Layout> |
|||
<Header style={{ padding: 0 }}> |
|||
<GlobalHeader |
|||
logo={logo} |
|||
currentUser={currentUser} |
|||
fetchingNotices={fetchingNotices} |
|||
notices={notices} |
|||
collapsed={collapsed} |
|||
isMobile={mb} |
|||
onNoticeClear={this.handleNoticeClear} |
|||
onCollapse={this.handleMenuCollapse} |
|||
onMenuClick={this.handleMenuClick} |
|||
onNoticeVisibleChange={this.handleNoticeVisibleChange} |
|||
/> |
|||
</Header> |
|||
<Content style={{ margin: '24px 24px 0', height: '100%' }}> |
|||
<Switch> |
|||
{redirectData.map(item => ( |
|||
<Redirect key={item.from} exact from={item.from} to={item.to} /> |
|||
))} |
|||
{getRoutes(match.path, routerData).map(item => ( |
|||
<AuthorizedRoute |
|||
key={item.key} |
|||
path={item.path} |
|||
component={item.component} |
|||
exact={item.exact} |
|||
authority={item.authority} |
|||
redirectPath="/exception/403" |
|||
/> |
|||
))} |
|||
<Redirect exact from="/" to={baseRedirect} /> |
|||
<Route render={NotFound} /> |
|||
</Switch> |
|||
</Content> |
|||
<Footer style={{ padding: 0 }}> |
|||
<GlobalFooter |
|||
links={[ |
|||
{ |
|||
key: 'Pro 首页', |
|||
title: 'Pro 首页', |
|||
href: 'http://pro.ant.design', |
|||
blankTarget: true, |
|||
}, |
|||
{ |
|||
key: 'github', |
|||
title: <Icon type="github" />, |
|||
href: 'https://github.com/ant-design/ant-design-pro', |
|||
blankTarget: true, |
|||
}, |
|||
{ |
|||
key: 'Ant Design', |
|||
title: 'Ant Design', |
|||
href: 'http://ant.design', |
|||
blankTarget: true, |
|||
}, |
|||
]} |
|||
copyright={ |
|||
<Fragment> |
|||
Copyright <Icon type="copyright" /> 2018 蚂蚁金服体验技术部出品 |
|||
</Fragment> |
|||
} |
|||
/> |
|||
</Footer> |
|||
</Layout> |
|||
</Layout> |
|||
); |
|||
|
|||
return ( |
|||
<DocumentTitle title={this.getPageTitle()}> |
|||
<ContainerQuery query={query}> |
|||
{params => <div className={classNames(params)}>{layout}</div>} |
|||
</ContainerQuery> |
|||
</DocumentTitle> |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,159 @@ |
|||
import React, { PureComponent } from 'react'; |
|||
import moment from 'moment'; |
|||
import { connect } from 'dva'; |
|||
import { |
|||
List, |
|||
Card, |
|||
Row, |
|||
Col, |
|||
Radio, |
|||
Input, |
|||
Progress, |
|||
Button, |
|||
Icon, |
|||
Dropdown, |
|||
Menu, |
|||
Avatar, |
|||
} from 'antd'; |
|||
|
|||
import PageHeaderLayout from '../../layouts/PageHeaderLayout'; |
|||
|
|||
import styles from './BasicList.less'; |
|||
|
|||
const RadioButton = Radio.Button; |
|||
const RadioGroup = Radio.Group; |
|||
const { Search } = Input; |
|||
|
|||
const ListContent = ({ data: { owner, createdAt, percent, status } }) => ( |
|||
<div className={styles.listContent}> |
|||
<div className={styles.listContentItem}> |
|||
<span>Owner</span> |
|||
<p>{owner}</p> |
|||
</div> |
|||
<div className={styles.listContentItem}> |
|||
<span>开始时间</span> |
|||
<p>{moment(createdAt).format('YYYY-MM-DD HH:mm')}</p> |
|||
</div> |
|||
<div className={styles.listContentItem}> |
|||
<Progress percent={percent} status={status} strokeWidth={6} style={{ width: 180 }} /> |
|||
</div> |
|||
</div> |
|||
); |
|||
|
|||
@connect(({ list, loading }) => ({ |
|||
list, |
|||
loading: loading.models.list, |
|||
})) |
|||
export default class BasicList extends PureComponent { |
|||
componentDidMount() { |
|||
const { dispatch } = this.props; |
|||
dispatch({ |
|||
type: 'list/fetch', |
|||
payload: { |
|||
count: 5, |
|||
}, |
|||
}); |
|||
} |
|||
|
|||
render() { |
|||
const { |
|||
list: { list }, |
|||
loading, |
|||
} = this.props; |
|||
|
|||
const Info = ({ title, value, bordered }) => ( |
|||
<div className={styles.headerInfo}> |
|||
<span>{title}</span> |
|||
<p>{value}</p> |
|||
{bordered && <em />} |
|||
</div> |
|||
); |
|||
|
|||
const extraContent = ( |
|||
<div className={styles.extraContent}> |
|||
<RadioGroup defaultValue="all"> |
|||
<RadioButton value="all">全部</RadioButton> |
|||
<RadioButton value="progress">进行中</RadioButton> |
|||
<RadioButton value="waiting">等待中</RadioButton> |
|||
</RadioGroup> |
|||
<Search className={styles.extraContentSearch} placeholder="请输入" onSearch={() => ({})} /> |
|||
</div> |
|||
); |
|||
|
|||
const paginationProps = { |
|||
showSizeChanger: true, |
|||
showQuickJumper: true, |
|||
pageSize: 5, |
|||
total: 50, |
|||
}; |
|||
|
|||
const menu = ( |
|||
<Menu> |
|||
<Menu.Item> |
|||
<a>编辑</a> |
|||
</Menu.Item> |
|||
<Menu.Item> |
|||
<a>删除</a> |
|||
</Menu.Item> |
|||
</Menu> |
|||
); |
|||
|
|||
const MoreBtn = () => ( |
|||
<Dropdown overlay={menu}> |
|||
<a> |
|||
更多 <Icon type="down" /> |
|||
</a> |
|||
</Dropdown> |
|||
); |
|||
|
|||
return ( |
|||
<PageHeaderLayout> |
|||
<div className={styles.standardList}> |
|||
<Card bordered={false}> |
|||
<Row> |
|||
<Col sm={8} xs={24}> |
|||
<Info title="我的待办" value="8个任务" bordered /> |
|||
</Col> |
|||
<Col sm={8} xs={24}> |
|||
<Info title="本周任务平均处理时间" value="32分钟" bordered /> |
|||
</Col> |
|||
<Col sm={8} xs={24}> |
|||
<Info title="本周完成任务数" value="24个任务" /> |
|||
</Col> |
|||
</Row> |
|||
</Card> |
|||
|
|||
<Card |
|||
className={styles.listCard} |
|||
bordered={false} |
|||
title="标准列表" |
|||
style={{ marginTop: 24 }} |
|||
bodyStyle={{ padding: '0 32px 40px 32px' }} |
|||
extra={extraContent} |
|||
> |
|||
<Button type="dashed" style={{ width: '100%', marginBottom: 8 }} icon="plus"> |
|||
添加 |
|||
</Button> |
|||
<List |
|||
size="large" |
|||
rowKey="id" |
|||
loading={loading} |
|||
pagination={paginationProps} |
|||
dataSource={list} |
|||
renderItem={item => ( |
|||
<List.Item actions={[<a>编辑</a>, <MoreBtn />]}> |
|||
<List.Item.Meta |
|||
avatar={<Avatar src={item.logo} shape="square" size="large" />} |
|||
title={<a href={item.href}>{item.title}</a>} |
|||
description={item.subDescription} |
|||
/> |
|||
<ListContent data={item} /> |
|||
</List.Item> |
|||
)} |
|||
/> |
|||
</Card> |
|||
</div> |
|||
</PageHeaderLayout> |
|||
); |
|||
} |
|||
} |
|||
Loading…
Reference in new issue