Просмотр исходного кода

User center (#940)

* temp save

* temp save

* user-center
ddcat1115 8 лет назад
Родитель
Сommit
3f00025e4c

+ 22 - 0
.roadhogrc.mock.js

@@ -30,6 +30,28 @@ const proxy = {
       userid: '00000001',
       email: 'antdesign@alipay.com',
       profile: '简单的介绍下自己',
+      signature: '海纳百川,有容乃大',
+      title: '交互专家',
+      group: '蚂蚁金服-平台数据技术事业群-基础平台部-用户体验技术部-UED',
+      tags: [{
+        key: '0',
+        label: '很有想法的',
+      }, {
+        key: '1',
+        label: '专注设计',
+      }, {
+        key: '2',
+        label: '辣~',
+      }, {
+        key: '3',
+        label: '大长腿',
+      }, {
+        key: '4',
+        label: '川妹子',
+      }, {
+        key: '5',
+        label: '海纳百川',
+      }],
       notifyCount: 12,
       country: 'China',
       geographic: {

+ 3 - 0
mock/api.js

@@ -88,14 +88,17 @@ export function fakeList(count) {
         {
           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',
         },
       ],
     });

+ 3 - 0
src/common/router.js

@@ -148,6 +148,9 @@ export const getRouterData = (app) => {
     '/exception/trigger': {
       component: dynamicWrapper(app, ['error'], () => import('../routes/Exception/triggerException')),
     },
+    '/user-center': {
+      component: dynamicWrapper(app, ['list', 'user', 'project'], () => import('../routes/UserCenter')),
+    },
     '/user': {
       component: dynamicWrapper(app, [], () => import('../layouts/UserLayout')),
     },

+ 1 - 1
src/components/GlobalHeader/index.js

@@ -57,7 +57,7 @@ export default class GlobalHeader extends PureComponent {
     } = this.props;
     const menu = (
       <Menu className={styles.menu} selectedKeys={[]} onClick={onMenuClick}>
-        <Menu.Item disabled><Icon type="user" />个人中心</Menu.Item>
+        <Menu.Item key="userCenter"><Icon type="user" />个人中心</Menu.Item>
         <Menu.Item key="userinfo"><Icon type="setting" />设置</Menu.Item>
         <Menu.Item key="triggerError"><Icon type="close-circle" />触发报错</Menu.Item>
         <Menu.Divider />

+ 4 - 0
src/layouts/BasicLayout.js

@@ -127,6 +127,10 @@ class BasicLayout extends React.PureComponent {
     });
   }
   handleMenuClick = ({ key }) => {
+    if (key === 'userCenter') {
+      this.props.dispatch(routerRedux.push('/user-center'));
+      return;
+    }
     if (key === 'triggerError') {
       this.props.dispatch(routerRedux.push('/exception/trigger'));
       return;

+ 1 - 12
src/routes/List/Applications.js

@@ -5,24 +5,13 @@ import { Row, Col, Form, Card, Select, Icon, Avatar, List, Tooltip, Dropdown, Me
 
 import StandardFormRow from '../../components/StandardFormRow';
 import TagSelect from '../../components/TagSelect';
+import { formatWan } from '../../utils/utils';
 
 import styles from './Applications.less';
 
 const { Option } = Select;
 const FormItem = Form.Item;
 
-const formatWan = (val) => {
-  const v = val * 1;
-  if (!v || isNaN(v)) return '';
-
-  let result = val;
-  if (val > 10000) {
-    result = Math.floor(val / 10000);
-    result = <span>{result}<em className={styles.wan}>万</em></span>;
-  }
-  return result;
-};
-
 /* eslint react/no-array-index-key: 0 */
 @Form.create()
 @connect(({ list, loading }) => ({

+ 0 - 9
src/routes/List/Applications.less

@@ -38,12 +38,3 @@
     }
   }
 }
-
-.wan {
-  position: relative;
-  top: -2px;
-  font-size: @font-size-base;
-  font-style: normal;
-  line-height: 20px;
-  margin-left: 2px;
-}

+ 293 - 0
src/routes/UserCenter.js

@@ -0,0 +1,293 @@
+import React, { PureComponent } from 'react';
+import { connect } from 'dva';
+import moment from 'moment';
+import numeral from 'numeral';
+import { List, Card, Row, Col, Icon, Dropdown,
+  Menu, Avatar, Tag, Divider, Tooltip, Spin } from 'antd';
+import AvatarList from '../components/AvatarList';
+import { formatWan } from '../utils/utils';
+import styles from './UserCenter.less';
+import stylesArticles from './List/Articles.less';
+import stylesApplications from './List/Applications.less';
+import stylesProjects from './List/Projects.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 UserCenter extends PureComponent {
+  state = {
+    key: 'article',
+  }
+
+  componentDidMount() {
+    const { dispatch } = this.props;
+    this.props.dispatch({
+      type: 'user/fetchCurrent',
+    });
+    dispatch({
+      type: 'list/fetch',
+      payload: {
+        count: 8,
+      },
+    });
+    dispatch({
+      type: 'project/fetchNotice',
+    });
+  }
+
+  onTabChange = (key) => {
+    this.setState({ key });
+  }
+
+  renderArticles = (list, loading) => {
+    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}
+        loading={loading}
+        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>
+        )}
+      />
+    );
+  }
+
+  renderApplications = (list, loading) => {
+    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 }}
+        loading={loading}
+        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>
+        )}
+      />
+    );
+  }
+
+  renderProjects = (list, loading) => {
+    return (
+      <List
+        className={stylesProjects.coverCardList}
+        rowKey="id"
+        loading={loading}
+        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} height={154} />}
+            >
+              <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>
+        )}
+      />
+    );
+  }
+
+  render() {
+    const { list: { list }, listLoading, currentUser, currentUserLoading,
+      project: { notice }, projectLoading } = this.props;
+    const operationTabList = [{
+      key: 'article',
+      tab: '文章(8)',
+    }, {
+      key: 'application',
+      tab: '应用(8)',
+    }, {
+      key: 'project',
+      tab: '项目(8)',
+    }];
+    const contentMap = {
+      article: this.renderArticles(list, listLoading),
+      application: this.renderApplications(list, listLoading),
+      project: this.renderProjects(list, listLoading),
+    };
+
+    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.map(item => <Tag key={item.key}>{item.label}</Tag>)
+                        }
+                        <Tag style={{ background: '#fff', borderStyle: 'dashed' }}>
+                          <Icon type="plus" />
+                        </Tag>
+                      </div>
+                      <Divider dashed />
+                      <div className={styles.team}>
+                        <div className={styles.teamTitle}>团队</div>
+                        <Spin spinning={projectLoading}>
+                          <Row gutter={36}>
+                            {
+                              notice.map(item => (
+                                <Col key={item.id} className={styles.item} lg={24} xl={12}>
+                                  <Avatar size="small" src={item.logo} />
+                                  {item.member}
+                                </Col>
+                              ))
+                            }
+                          </Row>
+                        </Spin>
+                      </div>
+                    </div>
+                  ) : 'loading...'
+              }
+            </Card>
+          </Col>
+          <Col lg={17} md={24}>
+            <Card
+              className={styles.tabsCard}
+              bordered={false}
+              tabList={operationTabList}
+              onTabChange={this.onTabChange}
+            >
+              {contentMap[this.state.key]}
+            </Card>
+          </Col>
+        </Row>
+      </div>
+    );
+  }
+}

+ 98 - 0
src/routes/UserCenter.less

@@ -0,0 +1,98 @@
+@import '~antd/lib/style/themes/default.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;
+    }
+  }
+
+  .item {
+    white-space: nowrap;
+    text-overflow: ellipsis;
+    overflow: hidden;
+    margin-bottom: 24px;
+  }
+}
+
+.tabsCard {
+  :global {
+    .ant-card-head {
+      padding: 0 16px;
+    }
+  }
+}
+
+.articleList {
+  :global {
+    .ant-list-item:first-child {
+      padding-top: 0;
+    }
+  }
+}

+ 28 - 0
src/utils/utils.js

@@ -1,4 +1,5 @@
 import moment from 'moment';
+import React from 'react';
 
 export function fixedZero(val) {
   return val * 1 < 10 ? `0${val}` : val;
@@ -157,3 +158,30 @@ const reg = /(((^https?:(?:\/\/)?)(?:[-;:&=\+\$,\w]+@)?[A-Za-z0-9.-]+|(?:www.|[-
 export function isUrl(path) {
   return reg.test(path);
 }
+
+export function formatWan(val) {
+  const v = val * 1;
+  if (!v || isNaN(v)) return '';
+
+  let result = val;
+  if (val > 10000) {
+    result = Math.floor(val / 10000);
+    result = (
+      <span>
+        {result}
+        <em styles={{
+            position: 'relative',
+            top: -2,
+            fontSize: 14,
+            fontStyle: 'normal',
+            lineHeight: 20,
+            marginLeft: 2,
+          }}
+        >
+          万
+        </em>
+      </span>
+    );
+  }
+  return result;
+}