Commit 8c52c611 authored by 陈帅's avatar 陈帅

fix AccountCenter block

parent 26dc3af8
......@@ -13,6 +13,7 @@
"dependencies": {
"antd": "^3.16.3",
"dva": "^2.4.0",
"numeral": "^2.0.6",
"react": "^16.6.3",
"umi-request": "^1.0.0"
},
......
import { ListItemDataType } from './data';
const titles = [
'Alipay',
'Angular',
......@@ -82,7 +84,96 @@ const getNotice = [
},
];
const covers = [
'https://gw.alipayobjects.com/zos/rmsportal/uMfMFlvUuceEyPpotzlq.png',
'https://gw.alipayobjects.com/zos/rmsportal/iZBVOIhGJiAnhplqjvZW.png',
'https://gw.alipayobjects.com/zos/rmsportal/iXjVmWVHbCJAyqvDxdtx.png',
'https://gw.alipayobjects.com/zos/rmsportal/gLaIAoVWTtLbBWZNYEMg.png',
];
const desc = [
'那是一种内在的东西, 他们到达不了,也无法触及的',
'希望是一个好东西,也许是最好的,好东西是不会消亡的',
'生命就像一盒巧克力,结果往往出人意料',
'城镇中有那么多的酒馆,她却偏偏走进了我的酒馆',
'那时候我只会想自己想要什么,从不想自己拥有什么',
];
const user = [
'付小小',
'曲丽丽',
'林东东',
'周星星',
'吴加好',
'朱偏右',
'鱼酱',
'乐哥',
'谭小仪',
'仲尼',
];
function fakeList(count: number): ListItemDataType[] {
const list = [];
for (let i = 0; i < count; i += 1) {
list.push({
id: `fake-list-${i}`,
owner: user[i % 10],
title: titles[i % 8],
avatar: avatars[i % 8],
cover: parseInt(i / 4 + '', 10) % 2 === 0 ? covers[i % 4] : covers[3 - (i % 4)],
status: ['active', 'exception', 'normal'][i % 3] as
| 'normal'
| 'exception'
| 'active'
| 'success',
percent: Math.ceil(Math.random() * 50) + 50,
logo: avatars[i % 8],
href: 'https://ant.design',
updatedAt: new Date(new Date().getTime() - 1000 * 60 * 60 * 2 * i).getTime(),
createdAt: new Date(new Date().getTime() - 1000 * 60 * 60 * 2 * i).getTime(),
subDescription: desc[i % 5],
description:
'在中台产品的研发过程中,会出现不同的设计规范和实现方式,但其中往往存在很多类似的页面和组件,这些类似的组件会被抽离成一套标准规范。',
activeUser: Math.ceil(Math.random() * 100000) + 100000,
newUser: Math.ceil(Math.random() * 1000) + 1000,
star: Math.ceil(Math.random() * 100) + 100,
like: Math.ceil(Math.random() * 100) + 100,
message: Math.ceil(Math.random() * 10) + 10,
content:
'段落示意:蚂蚁金服设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态,提供跨越设计与开发的体验解决方案。蚂蚁金服设计平台 ant.design,用最小的工作量,无缝接入蚂蚁金服生态,提供跨越设计与开发的体验解决方案。',
members: [
{
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/ZiESqWwCXBRQoaPONSJe.png',
name: '曲丽丽',
id: 'member1',
},
{
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/tBOxZPlITHqwlGjsJWaF.png',
name: '王昭君',
id: 'member2',
},
{
avatar: 'https://gw.alipayobjects.com/zos/rmsportal/sBxjgqiuHMGRkIjqlQCd.png',
name: '董娜娜',
id: 'member3',
},
],
});
}
return list;
}
function getFakeList(req: { query: any }, res: { json: (arg0: ListItemDataType[]) => void }) {
const params = req.query;
const count = params.count * 1 || 20;
const result = fakeList(count);
return res.json(result);
}
export default {
'GET /api/BLOCK_NAME/fake_list': getFakeList,
// 支持值为 Object 和 Array
'GET /api/BLOCK_NAME/currentUser': {
name: 'Serati Ma',
......
@import '~antd/lib/style/themes/default.less';
.filterCardList {
margin-bottom: -24px;
:global {
.ant-card-meta-content {
margin-top: 0;
}
// disabled white space
.ant-card-meta-avatar {
font-size: 0;
}
.ant-card-actions {
background: #f7f9fa;
}
.ant-list .ant-list-item-content-single {
max-width: 100%;
}
}
.cardInfo {
margin-top: 16px;
margin-left: 40px;
zoom: 1;
&::before,
&::after {
display: table;
content: ' ';
}
&::after {
clear: both;
height: 0;
font-size: 0;
visibility: hidden;
}
& > div {
position: relative;
float: left;
width: 50%;
text-align: left;
p {
margin: 0;
font-size: 24px;
line-height: 32px;
}
p:first-child {
margin-bottom: 4px;
color: @text-color-secondary;
font-size: 12px;
line-height: 20px;
}
}
}
}
import React, { Component } from 'react';
import { List, Card, Icon, Dropdown, Menu, Avatar, Tooltip } from 'antd';
import numeral from 'numeral';
import { connect } from 'dva';
import stylesApplications from './index.less';
import { ModalState } from '../../model';
export function formatWan(val: number) {
const v = val * 1;
if (!v || Number.isNaN(v)) return '';
let result: React.ReactNode = val;
if (val > 10000) {
result = (
<span>
{Math.floor(val / 10000)}
<span
style={{
position: 'relative',
top: -2,
fontSize: 14,
fontStyle: 'normal',
marginLeft: 2,
}}
>
</span>
</span>
);
}
return result;
}
@connect(({ BLOCK_NAME_CAMEL_CASE }: { BLOCK_NAME_CAMEL_CASE: ModalState }) => ({
list: BLOCK_NAME_CAMEL_CASE.list,
}))
class Applications extends Component<Partial<ModalState>> {
render() {
const { list } = this.props;
const itemMenu = (
<Menu>
<Menu.Item>
<a target="_blank" rel="noopener noreferrer" href="https://www.alipay.com/">
1st menu item
</a>
</Menu.Item>
<Menu.Item>
<a target="_blank" rel="noopener noreferrer" href="https://www.taobao.com/">
2nd menu item
</a>
</Menu.Item>
<Menu.Item>
<a target="_blank" rel="noopener noreferrer" href="https://www.tmall.com/">
3d menu item
</a>
</Menu.Item>
</Menu>
);
const CardInfo: React.SFC<{
activeUser: React.ReactNode;
newUser: React.ReactNode;
}> = ({ 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 key="download" title="下载">
<Icon type="download" />
</Tooltip>,
<Tooltip title="编辑" key="edit">
<Icon type="edit" />
</Tooltip>,
<Tooltip title="分享" key="share">
<Icon type="share-alt" />
</Tooltip>,
<Dropdown overlay={itemMenu} key="ellipsis">
<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>
)}
/>
);
}
}
export default Applications;
@import '~antd/lib/style/themes/default.less';
.listContent {
.description {
max-width: 720px;
line-height: 22px;
}
.extra {
margin-top: 16px;
color: @text-color-secondary;
line-height: 22px;
& > :global(.ant-avatar) {
position: relative;
top: 1px;
width: 20px;
height: 20px;
margin-right: 8px;
vertical-align: top;
}
& > em {
margin-left: 16px;
color: @disabled-color;
font-style: normal;
}
}
}
@media screen and (max-width: @screen-xs) {
.listContent {
.extra {
& > em {
display: block;
margin-top: 8px;
margin-left: 0;
}
}
}
}
import React from 'react';
import moment from 'moment';
import { Avatar } from 'antd';
import styles from './index.less';
export interface ApplicationsProps {
data: {
content?: string;
updatedAt?: any;
avatar?: string;
owner?: string;
href?: string;
};
}
const ArticleListContent: React.SFC<ApplicationsProps> = ({
data: { content, updatedAt, avatar, owner, href },
}) => (
<div className={styles.listContent}>
<div className={styles.description}>{content}</div>
<div className={styles.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>
);
export default ArticleListContent;
@import '~antd/lib/style/themes/default.less';
.articleList {
:global {
.ant-list-item:first-child {
padding-top: 0;
}
}
}
a.listItemMetaTitle {
color: @heading-color;
}
import React, { Component } from 'react';
import { List, Icon, Tag } from 'antd';
import { connect } from 'dva';
import ArticleListContent from '../ArticleListContent';
import styles from './index.less';
import { ModalState } from '../../model';
import { ListItemDataType } from '../../data';
@connect(({ BLOCK_NAME_CAMEL_CASE }: { BLOCK_NAME_CAMEL_CASE: ModalState }) => ({
list: BLOCK_NAME_CAMEL_CASE.list,
}))
class Articles extends Component<Partial<ModalState>> {
render() {
const { list } = this.props;
const IconText: React.SFC<{
type: string;
text: React.ReactNode;
}> = ({ type, text }) => (
<span>
<Icon type={type} style={{ marginRight: 8 }} />
{text}
</span>
);
return (
<List<ListItemDataType>
size="large"
className={styles.articleList}
rowKey="id"
itemLayout="vertical"
dataSource={list}
renderItem={item => (
<List.Item
key={item.id}
actions={[
<IconText key="star" type="star-o" text={item.star} />,
<IconText key="like" type="like-o" text={item.like} />,
<IconText key="message" type="message" text={item.message} />,
]}
>
<List.Item.Meta
title={
<a className={styles.listItemMetaTitle} href={item.href}>
{item.title}
</a>
}
description={
<span>
<Tag>Ant Design</Tag>
<Tag>设计语言</Tag>
<Tag>蚂蚁金服</Tag>
</span>
}
/>
<ArticleListContent data={item} />
</List.Item>
)}
/>
);
}
}
export default Articles;
@import '~antd/lib/style/themes/default.less';
.avatarList {
display: inline-block;
ul {
display: inline-block;
margin-left: 8px;
font-size: 0;
}
}
.avatarItem {
display: inline-block;
width: @avatar-size-base;
height: @avatar-size-base;
margin-left: -8px;
font-size: @font-size-base;
:global {
.ant-avatar {
border: 1px solid #fff;
}
}
}
.avatarItemLarge {
width: @avatar-size-lg;
height: @avatar-size-lg;
}
.avatarItemSmall {
width: @avatar-size-sm;
height: @avatar-size-sm;
}
.avatarItemMini {
width: 20px;
height: 20px;
:global {
.ant-avatar {
width: 20px;
height: 20px;
line-height: 20px;
.ant-avatar-string {
font-size: 12px;
line-height: 18px;
}
}
}
}
import React from 'react';
import { Tooltip, Avatar } from 'antd';
import classNames from 'classnames';
import styles from './index.less';
export declare type SizeType = number | 'small' | 'default' | 'large';
export interface AvatarItemProps {
tips: React.ReactNode;
src: string;
size?: SizeType;
style?: React.CSSProperties;
onClick?: () => void;
}
export interface AvatarListProps {
Item?: React.ReactElement<AvatarItemProps>;
size?: SizeType;
maxLength?: number;
excessItemsStyle?: React.CSSProperties;
style?: React.CSSProperties;
children: React.ReactElement<AvatarItemProps> | Array<React.ReactElement<AvatarItemProps>>;
}
const avatarSizeToClassName = (size?: SizeType) =>
classNames(styles.avatarItem, {
[styles.avatarItemLarge]: size === 'large',
[styles.avatarItemSmall]: size === 'small',
[styles.avatarItemMini]: size === 'mini',
});
const Item: React.SFC<AvatarItemProps> = ({ src, size, tips, onClick = () => {} }) => {
const cls = avatarSizeToClassName(size);
return (
<li className={cls} onClick={onClick}>
{tips ? (
<Tooltip title={tips}>
<Avatar src={src} size={size} style={{ cursor: 'pointer' }} />
</Tooltip>
) : (
<Avatar src={src} size={size} />
)}
</li>
);
};
const AvatarList: React.SFC<AvatarListProps> & { Item: typeof Item } = ({
children,
size,
maxLength = 5,
excessItemsStyle,
...other
}) => {
const numOfChildren = React.Children.count(children);
const numToShow = maxLength >= numOfChildren ? numOfChildren : maxLength;
const childrenArray = React.Children.toArray(children) as Array<
React.ReactElement<AvatarItemProps>
>;
const childrenWithProps = childrenArray.slice(0, numToShow).map(child =>
React.cloneElement(child, {
size,
}),
);
if (numToShow < numOfChildren) {
const cls = avatarSizeToClassName(size);
childrenWithProps.push(
<li key="exceed" className={cls}>
<Avatar size={size} style={excessItemsStyle}>{`+${numOfChildren - maxLength}`}</Avatar>
</li>,
);
}
return (
<div {...other} className={styles.avatarList}>
<ul> {childrenWithProps} </ul>
</div>
);
};
AvatarList.Item = Item;
export default AvatarList;
@import '~antd/lib/style/themes/default.less';
.coverCardList {
margin-bottom: -24px;
.card {
:global {
.ant-card-meta-title {
margin-bottom: 4px;
& > a {
display: inline-block;
max-width: 100%;
color: @heading-color;
}
}
.ant-card-meta-description {
height: 44px;
overflow: hidden;
line-height: 22px;
}
}
&:hover {
:global {
.ant-card-meta-title > a {
color: @primary-color;
}
}
}
}
.cardItemContent {
display: flex;
height: 20px;
margin-top: 16px;
margin-bottom: -4px;
line-height: 20px;
& > span {
flex: 1;
color: @text-color-secondary;
font-size: 12px;
}
.avatarList {
flex: 0 1 auto;
}
}
.cardList {
margin-top: 24px;
}
:global {
.ant-list .ant-list-item-content-single {
max-width: 100%;
}
}
}
import React, { Component } from 'react';
import { List, Card } from 'antd';
import moment from 'moment';
import { connect } from 'dva';
import AvatarList from '../AvatarList';
import styles from './index.less';
import { ModalState } from '../../model';
import { ListItemDataType } from '../../data';
@connect(({ BLOCK_NAME_CAMEL_CASE }: { BLOCK_NAME_CAMEL_CASE: ModalState }) => ({
list: BLOCK_NAME_CAMEL_CASE.list,
}))
class Projects extends Component<Partial<ModalState>> {
render() {
const { list } = this.props;
return (
<List<ListItemDataType>
className={styles.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={styles.card}
hoverable
cover={<img alt={item.title} src={item.cover} />}
>
<Card.Meta title={<a>{item.title}</a>} description={item.subDescription} />
<div className={styles.cardItemContent}>
<span>{moment(item.updatedAt).fromNow()}</span>
<div className={styles.avatarList}>
<AvatarList size="small">
{item.members.map(member => (
<AvatarList.Item
key={`${item.id}-avatar-${member.id}`}
src={member.avatar}
tips={member.name}
/>
))}
</AvatarList>
</div>
</div>
</Card>
</List.Item>
)}
/>
);
}
}
export default Projects;
......@@ -46,3 +46,33 @@ export interface CurrentUser {
address: string;
phone: string;
}
export interface Member {
avatar: string;
name: string;
id: string;
}
export interface ListItemDataType {
id: string;
owner: string;
title: string;
avatar: string;
cover: string;
status: 'normal' | 'exception' | 'active' | 'success';
percent: number;
logo: string;
href: string;
body?: any;
updatedAt: number;
createdAt: number;
subDescription: string;
description: string;
activeUser: number;
newUser: number;
star: number;
like: number;
message: number;
content: string;
members: Member[];
}
......@@ -7,6 +7,9 @@ import { Card, Row, Col, Icon, Avatar, Tag, Divider, Input } from 'antd';
import styles from './Center.less';
import { ITag, CurrentUser } from './data';
import { ModalState } from './model';
import Articles from './components/Articles';
import Applications from './components/Applications';
import Projects from './components/Projects';
const operationTabList = [
{
......@@ -42,7 +45,7 @@ interface BLOCK_NAME_CAMEL_CASEProps extends RouteChildrenProps {
}
interface BLOCK_NAME_CAMEL_CASEState {
newTags: ITag[];
tabKey: string;
tabKey: 'articles' | 'applications' | 'projects';
inputVisible: boolean;
inputValue: string;
}
......@@ -95,6 +98,9 @@ class PAGE_NAME_UPPER_CAMEL_CASE extends PureComponent<
dispatch({
type: 'BLOCK_NAME_CAMEL_CASE/fetchCurrent',
});
dispatch({
type: 'BLOCK_NAME_CAMEL_CASE/fetch',
});
}
onTabChange = (key: string) => {
......@@ -102,7 +108,7 @@ class PAGE_NAME_UPPER_CAMEL_CASE extends PureComponent<
// const { match } = this.props;
// router.push(`${match.url}/${key}`);
this.setState({
tabKey: key,
tabKey: key as BLOCK_NAME_CAMEL_CASEState['tabKey'],
});
};
......@@ -131,10 +137,21 @@ class PAGE_NAME_UPPER_CAMEL_CASE extends PureComponent<
inputValue: '',
});
};
renderChildrenByTabKey = (tabKey: BLOCK_NAME_CAMEL_CASEState['tabKey']) => {
if (tabKey === 'projects') {
return <Projects />;
}
if (tabKey === 'applications') {
return <Applications />;
}
if (tabKey === 'articles') {
return <Articles />;
}
return null;
};
render() {
const { newTags, inputVisible, inputValue, tabKey } = this.state;
const { currentUser, currentUserLoading, children } = this.props;
const { currentUser, currentUserLoading } = this.props;
const dataLoading = currentUserLoading || !(currentUser && Object.keys(currentUser).length);
return (
<Row gutter={24}>
......@@ -215,7 +232,7 @@ class PAGE_NAME_UPPER_CAMEL_CASE extends PureComponent<
activeTabKey={tabKey}
onTabChange={this.onTabChange}
>
{children || tabKey}
{this.renderChildrenByTabKey(tabKey)}
</Card>
</Col>
</Row>
......
import { queryCurrent } from './service';
import { CurrentUser } from './data';
import { queryCurrent, queryFakeList } from './service';
import { CurrentUser, ListItemDataType } from './data';
export interface ModalState {
currentUser: Partial<CurrentUser>;
list: ListItemDataType[];
}
import { Reducer } from 'redux';
......@@ -19,9 +20,11 @@ export interface ModelType {
state: ModalState;
effects: {
fetchCurrent: Effect;
fetch: Effect;
};
reducers: {
saveCurrentUser: Reducer<ModalState>;
queryList: Reducer<ModalState>;
};
}
......@@ -30,6 +33,7 @@ const Model: ModelType = {
state: {
currentUser: {},
list: [],
},
effects: {
......@@ -40,15 +44,28 @@ const Model: ModelType = {
payload: response,
});
},
*fetch({ payload }, { call, put }) {
const response = yield call(queryFakeList, payload);
yield put({
type: 'queryList',
payload: Array.isArray(response) ? response : [],
});
},
},
reducers: {
saveCurrentUser(state, action) {
return {
...state,
...state!,
currentUser: action.payload || {},
};
},
queryList(state, action) {
return {
...state!,
list: action.payload,
};
},
},
};
......
......@@ -3,3 +3,9 @@ import request from 'umi-request';
export async function queryCurrent() {
return request('/api/BLOCK_NAME/currentUser');
}
export async function queryFakeList(params: { count: number }) {
return request(`/api/BLOCK_NAME/fake_list`, {
params,
});
}
@import '~antd/lib/style/themes/default.less';
@import './utils/utils.less';
.iconGroup {
i {
......@@ -18,11 +17,21 @@
padding: 0;
list-style: none;
li {
.clearfix();
display: flex;
align-items: center;
margin-top: 16px;
zoom: 1;
&::before,
&::after {
display: table;
content: ' ';
}
&::after {
clear: both;
height: 0;
font-size: 0;
visibility: hidden;
}
span {
color: @text-color;
font-size: 14px;
......
export interface member {
export interface Member {
avatar: string;
name: string;
id: string;
}
export interface cardlistitemdatatype {
export interface CardListItemDataType {
id: string;
owner: string;
title: string;
......
@import '~antd/lib/style/themes/default.less';
@import './utils/utils.less';
.filterCardList {
margin-bottom: -24px;
......@@ -19,10 +18,20 @@
}
}
.cardInfo {
.clearfix();
margin-top: 16px;
margin-left: 40px;
zoom: 1;
&::before,
&::after {
display: table;
content: ' ';
}
&::after {
clear: both;
height: 0;
font-size: 0;
visibility: hidden;
}
& > div {
position: relative;
float: left;
......
{
"private": true,
"scripts": {
"dev": "cross-env PAGES_PATH='EditorKoni/src' umi dev",
"dev": "cross-env PAGES_PATH='AccountCenter/src' umi dev",
"lint": "npm run lint:ts && npm run lint:style && npm run lint:prettier",
"lint-staged": "lint-staged",
"lint-staged:ts": "tslint",
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment