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