committed by
jim
7 changed files with 497 additions and 1 deletions
@ -0,0 +1,67 @@ |
|||
import React from 'react'; |
|||
import { List, Card, Icon, Dropdown, Menu, Avatar, Tooltip } from 'antd'; |
|||
import numeral from 'numeral'; |
|||
import { formatWan } from '../../../utils/utils'; |
|||
import stylesApplications from '../../List/Applications.less'; |
|||
|
|||
export default (props) => { |
|||
const { list } = props; |
|||
const itemMenu = ( |
|||
<Menu> |
|||
<Menu.Item> |
|||
<a target="_blank" rel="noopener noreferrer" href="http://www.alipay.com/">1st menu item</a> |
|||
</Menu.Item> |
|||
<Menu.Item> |
|||
<a target="_blank" rel="noopener noreferrer" href="http://www.taobao.com/">2nd menu item</a> |
|||
</Menu.Item> |
|||
<Menu.Item> |
|||
<a target="_blank" rel="noopener noreferrer" href="http://www.tmall.com/">3d menu item</a> |
|||
</Menu.Item> |
|||
</Menu> |
|||
); |
|||
const CardInfo = ({ activeUser, newUser }) => ( |
|||
<div className={stylesApplications.cardInfo}> |
|||
<div> |
|||
<p>活跃用户</p> |
|||
<p>{activeUser}</p> |
|||
</div> |
|||
<div> |
|||
<p>新增用户</p> |
|||
<p>{newUser}</p> |
|||
</div> |
|||
</div> |
|||
); |
|||
return ( |
|||
<List |
|||
rowKey="id" |
|||
className={stylesApplications.filterCardList} |
|||
grid={{ gutter: 24, xxl: 3, xl: 2, lg: 2, md: 2, sm: 2, xs: 1 }} |
|||
dataSource={list} |
|||
renderItem={item => ( |
|||
<List.Item key={item.id}> |
|||
<Card |
|||
hoverable |
|||
bodyStyle={{ paddingBottom: 20 }} |
|||
actions={[ |
|||
<Tooltip title="下载"><Icon type="download" /></Tooltip>, |
|||
<Tooltip title="编辑"><Icon type="edit" /></Tooltip>, |
|||
<Tooltip title="分享"><Icon type="share-alt" /></Tooltip>, |
|||
<Dropdown overlay={itemMenu}><Icon type="ellipsis" /></Dropdown>, |
|||
]} |
|||
> |
|||
<Card.Meta |
|||
avatar={<Avatar size="small" src={item.avatar} />} |
|||
title={item.title} |
|||
/> |
|||
<div className={stylesApplications.cardItemContent}> |
|||
<CardInfo |
|||
activeUser={formatWan(item.activeUser)} |
|||
newUser={numeral(item.newUser).format('0,0')} |
|||
/> |
|||
</div> |
|||
</Card> |
|||
</List.Item> |
|||
)} |
|||
/> |
|||
); |
|||
}; |
|||
@ -0,0 +1,57 @@ |
|||
import React from 'react'; |
|||
import { List, Icon, Avatar, Tag } from 'antd'; |
|||
import moment from 'moment'; |
|||
import stylesArticles from '../../List/Articles.less'; |
|||
import styles from './Articles.less'; |
|||
|
|||
export default (props) => { |
|||
const { list } = props; |
|||
const IconText = ({ type, text }) => ( |
|||
<span> |
|||
<Icon type={type} style={{ marginRight: 8 }} /> |
|||
{text} |
|||
</span> |
|||
); |
|||
const ListContent = ({ data: { content, updatedAt, avatar, owner, href } }) => ( |
|||
<div className={stylesArticles.listContent}> |
|||
<div className={stylesArticles.description}>{content}</div> |
|||
<div className={stylesArticles.extra}> |
|||
<Avatar src={avatar} size="small" /><a href={href}>{owner}</a> 发布在 <a href={href}>{href}</a> |
|||
<em>{moment(updatedAt).format('YYYY-MM-DD HH:mm')}</em> |
|||
</div> |
|||
</div> |
|||
); |
|||
return ( |
|||
<List |
|||
size="large" |
|||
className={styles.articleList} |
|||
rowKey="id" |
|||
itemLayout="vertical" |
|||
dataSource={list} |
|||
renderItem={item => ( |
|||
<List.Item |
|||
key={item.id} |
|||
actions={[ |
|||
<IconText type="star-o" text={item.star} />, |
|||
<IconText type="like-o" text={item.like} />, |
|||
<IconText type="message" text={item.message} />, |
|||
]} |
|||
> |
|||
<List.Item.Meta |
|||
title={( |
|||
<a className={stylesArticles.listItemMetaTitle} href={item.href}>{item.title}</a> |
|||
)} |
|||
description={ |
|||
<span> |
|||
<Tag>Ant Design</Tag> |
|||
<Tag>设计语言</Tag> |
|||
<Tag>蚂蚁金服</Tag> |
|||
</span> |
|||
} |
|||
/> |
|||
<ListContent data={item} /> |
|||
</List.Item> |
|||
)} |
|||
/> |
|||
); |
|||
}; |
|||
@ -0,0 +1,7 @@ |
|||
.articleList { |
|||
:global { |
|||
.ant-list-item:first-child { |
|||
padding-top: 0; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,213 @@ |
|||
import React, { PureComponent } from 'react'; |
|||
import { connect } from 'dva'; |
|||
import { Link, routerRedux, Route, Switch, Redirect } from 'dva/router'; |
|||
import { Card, Row, Col, Icon, Avatar, Tag, Divider, Spin, Input } from 'antd'; |
|||
import { getRoutes } from '../../../utils/utils'; |
|||
import styles from './Center.less'; |
|||
|
|||
@connect(({ list, loading, user, project }) => ({ |
|||
list, |
|||
listLoading: loading.effects['list/fetch'], |
|||
currentUser: user.currentUser, |
|||
currentUserLoading: loading.effects['user/fetchCurrent'], |
|||
project, |
|||
projectLoading: loading.effects['project/fetchNotice'], |
|||
})) |
|||
export default class Center extends PureComponent { |
|||
state = { |
|||
newTags: [], |
|||
inputVisible: false, |
|||
inputValue: '', |
|||
} |
|||
|
|||
componentDidMount() { |
|||
const { dispatch } = this.props; |
|||
this.props.dispatch({ |
|||
type: 'user/fetchCurrent', |
|||
}); |
|||
dispatch({ |
|||
type: 'list/fetch', |
|||
payload: { |
|||
count: 8, |
|||
}, |
|||
}); |
|||
dispatch({ |
|||
type: 'project/fetchNotice', |
|||
}); |
|||
} |
|||
|
|||
onTabChange = (key) => { |
|||
const { dispatch, match } = this.props; |
|||
switch (key) { |
|||
case 'articles': |
|||
dispatch(routerRedux.push(`${match.url}/articles`)); |
|||
break; |
|||
case 'applications': |
|||
dispatch(routerRedux.push(`${match.url}/applications`)); |
|||
break; |
|||
case 'projects': |
|||
dispatch(routerRedux.push(`${match.url}/projects`)); |
|||
break; |
|||
default: |
|||
break; |
|||
} |
|||
} |
|||
|
|||
showInput = () => { |
|||
this.setState({ inputVisible: true }, () => this.input.focus()); |
|||
} |
|||
|
|||
saveInputRef = (input) => { |
|||
this.input = input; |
|||
} |
|||
|
|||
handleInputChange = (e) => { |
|||
this.setState({ inputValue: e.target.value }); |
|||
} |
|||
|
|||
handleInputConfirm = () => { |
|||
const { state } = this; |
|||
const { inputValue } = state; |
|||
let { newTags } = state; |
|||
if (inputValue && newTags.filter(tag => tag.label === inputValue).length === 0) { |
|||
newTags = [...newTags, { key: `new-${newTags.length}`, label: inputValue }]; |
|||
} |
|||
this.setState({ |
|||
newTags, |
|||
inputVisible: false, |
|||
inputValue: '', |
|||
}); |
|||
} |
|||
|
|||
render() { |
|||
const { newTags, inputVisible, inputValue } = this.state; |
|||
const { list: { list }, listLoading, currentUser, currentUserLoading, |
|||
project: { notice }, projectLoading, match, routerData, location } = this.props; |
|||
const routes = getRoutes(match.path, routerData); |
|||
|
|||
const operationTabList = [{ |
|||
key: 'articles', |
|||
tab: <span>文章 <span style={{ fontSize: 14 }}>(8)</span></span>, |
|||
}, { |
|||
key: 'applications', |
|||
tab: <span>应用 <span style={{ fontSize: 14 }}>(8)</span></span>, |
|||
}, { |
|||
key: 'projects', |
|||
tab: <span>项目 <span style={{ fontSize: 14 }}>(8)</span></span>, |
|||
}]; |
|||
|
|||
return ( |
|||
<div className={styles.userCenter}> |
|||
<Row gutter={24}> |
|||
<Col lg={7} md={24}> |
|||
<Card |
|||
bordered={false} |
|||
style={{ marginBottom: 24 }} |
|||
loading={currentUserLoading} |
|||
> |
|||
{ |
|||
currentUser && Object.keys(currentUser).length ? |
|||
( |
|||
<div> |
|||
<div className={styles.avatarHolder}> |
|||
<img alt="" src={currentUser.avatar} /> |
|||
<div className={styles.name}>{currentUser.name}</div> |
|||
<div>{currentUser.signature}</div> |
|||
</div> |
|||
<div className={styles.detail}> |
|||
<p><i className={styles.title} />{currentUser.title}</p> |
|||
<p><i className={styles.group} />{currentUser.group}</p> |
|||
<p><i className={styles.address} /> |
|||
{currentUser.geographic.province.label} |
|||
{currentUser.geographic.city.label} |
|||
</p> |
|||
</div> |
|||
<Divider dashed /> |
|||
<div className={styles.tags}> |
|||
<div className={styles.tagsTitle}>标签</div> |
|||
{ |
|||
currentUser.tags.concat(newTags).map(item => |
|||
<Tag key={item.key}>{item.label}</Tag>) |
|||
} |
|||
{inputVisible && ( |
|||
<Input |
|||
ref={this.saveInputRef} |
|||
type="text" |
|||
size="small" |
|||
style={{ width: 78 }} |
|||
value={inputValue} |
|||
onChange={this.handleInputChange} |
|||
onBlur={this.handleInputConfirm} |
|||
onPressEnter={this.handleInputConfirm} |
|||
/> |
|||
)} |
|||
{!inputVisible && ( |
|||
<Tag |
|||
onClick={this.showInput} |
|||
style={{ background: '#fff', borderStyle: 'dashed' }} |
|||
> |
|||
<Icon type="plus" /> |
|||
</Tag> |
|||
)} |
|||
</div> |
|||
<Divider style={{ marginTop: 16 }} dashed /> |
|||
<div className={styles.team}> |
|||
<div className={styles.teamTitle}>团队</div> |
|||
<Spin spinning={projectLoading}> |
|||
<Row gutter={36}> |
|||
{ |
|||
notice.map(item => ( |
|||
<Col key={item.id} lg={24} xl={12}> |
|||
<Link to={item.href}> |
|||
<Avatar size="small" src={item.logo} /> |
|||
{item.member} |
|||
</Link> |
|||
</Col> |
|||
)) |
|||
} |
|||
</Row> |
|||
</Spin> |
|||
</div> |
|||
</div> |
|||
) : 'loading...' |
|||
} |
|||
</Card> |
|||
</Col> |
|||
<Col lg={17} md={24}> |
|||
<Card |
|||
className={styles.tabsCard} |
|||
bordered={false} |
|||
tabList={operationTabList} |
|||
activeTabKey={location.pathname.replace(`${match.path}/`, '')} |
|||
onTabChange={this.onTabChange} |
|||
loading={listLoading} |
|||
> |
|||
<Switch> |
|||
{ |
|||
routes.map(item => |
|||
( |
|||
<Route |
|||
key={item.key} |
|||
path={item.path} |
|||
render={props => ( |
|||
<item.component {...props} list={list} /> |
|||
)} |
|||
exact={item.exact} |
|||
/> |
|||
) |
|||
) |
|||
} |
|||
<Redirect |
|||
exact |
|||
from="/account/center" |
|||
to="/account/center/articles" |
|||
/> |
|||
<Redirect to="/exception/404" /> |
|||
</Switch> |
|||
</Card> |
|||
</Col> |
|||
</Row> |
|||
</div> |
|||
); |
|||
} |
|||
} |
|||
@ -0,0 +1,96 @@ |
|||
@import '~antd/lib/style/themes/default.less'; |
|||
@import "../../../utils/utils.less"; |
|||
|
|||
.avatarHolder { |
|||
text-align: center; |
|||
margin-bottom: 24px; |
|||
|
|||
& > img { |
|||
width: 104px; |
|||
height: 104px; |
|||
margin-bottom: 20px; |
|||
} |
|||
|
|||
.name { |
|||
font-size: 20px; |
|||
line-height: 28px; |
|||
font-weight: 500; |
|||
color: @heading-color; |
|||
margin-bottom: 4px; |
|||
} |
|||
} |
|||
|
|||
.detail { |
|||
p { |
|||
margin-bottom: 8px; |
|||
padding-left: 26px; |
|||
position: relative; |
|||
|
|||
&:last-child { |
|||
margin-bottom: 0; |
|||
} |
|||
} |
|||
|
|||
i { |
|||
position: absolute; |
|||
height: 14px; |
|||
width: 14px; |
|||
left: 0; |
|||
top: 4px; |
|||
background: url(https://gw.alipayobjects.com/zos/rmsportal/pBjWzVAHnOOtAUvZmZfy.svg); |
|||
|
|||
&.title { |
|||
background-position: 0 0; |
|||
} |
|||
|
|||
&.group { |
|||
background-position: 0 -22px; |
|||
} |
|||
|
|||
&.address { |
|||
background-position: 0 -44px; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.tagsTitle, .teamTitle { |
|||
font-weight: 500; |
|||
color: @heading-color; |
|||
margin-bottom: 12px; |
|||
} |
|||
|
|||
.tags { |
|||
:global { |
|||
.ant-tag { |
|||
margin-bottom: 8px; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.team { |
|||
:global { |
|||
.ant-avatar { |
|||
margin-right: 12px; |
|||
} |
|||
} |
|||
|
|||
a { |
|||
display: block; |
|||
margin-bottom: 24px; |
|||
color: @text-color; |
|||
transition: color .3s; |
|||
.textOverflow(); |
|||
|
|||
&:hover { |
|||
color: @primary-color; |
|||
} |
|||
} |
|||
} |
|||
|
|||
.tabsCard { |
|||
:global { |
|||
.ant-card-head { |
|||
padding: 0 16px; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,47 @@ |
|||
import React from 'react'; |
|||
import { List, Card } from 'antd'; |
|||
import moment from 'moment'; |
|||
import AvatarList from '../../../components/AvatarList'; |
|||
import stylesProjects from '../../List/Projects.less'; |
|||
|
|||
export default (props) => { |
|||
const { list } = props; |
|||
return ( |
|||
<List |
|||
className={stylesProjects.coverCardList} |
|||
rowKey="id" |
|||
grid={{ gutter: 24, xxl: 3, xl: 2, lg: 2, md: 2, sm: 2, xs: 1 }} |
|||
dataSource={list} |
|||
renderItem={item => ( |
|||
<List.Item> |
|||
<Card |
|||
className={stylesProjects.card} |
|||
hoverable |
|||
cover={<img alt={item.title} src={item.cover} />} |
|||
> |
|||
<Card.Meta |
|||
title={<a href="#">{item.title}</a>} |
|||
description={item.subDescription} |
|||
/> |
|||
<div className={stylesProjects.cardItemContent}> |
|||
<span>{moment(item.updatedAt).fromNow()}</span> |
|||
<div className={stylesProjects.avatarList}> |
|||
<AvatarList size="mini"> |
|||
{ |
|||
item.members.map(member => ( |
|||
<AvatarList.Item |
|||
key={`${item.id}-avatar-${member.id}`} |
|||
src={member.avatar} |
|||
tips={member.name} |
|||
/> |
|||
)) |
|||
} |
|||
</AvatarList> |
|||
</div> |
|||
</div> |
|||
</Card> |
|||
</List.Item> |
|||
)} |
|||
/> |
|||
); |
|||
}; |
|||
Loading…
Reference in new issue