Преглед на файлове

New Login component (#147)

* add Login component

* update

* refactor

* update Login component & reactor Login page

* fix test case

* update

* update

* fix code style

* fix
ddcat1115 преди 8 години
родител
ревизия
1a5b7acabf

+ 1 - 0
package.json

@@ -33,6 +33,7 @@
     "lodash-decorators": "^4.4.1",
     "moment": "^2.19.1",
     "numeral": "^2.0.6",
+    "omit.js": "^1.0.0",
     "prop-types": "^15.5.10",
     "qs": "^6.5.0",
     "rc-drawer-menu": "^0.5.0",

+ 104 - 0
src/components/Login/LoginItem.js

@@ -0,0 +1,104 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { Form, Button, Row, Col } from 'antd';
+import omit from 'omit.js';
+import styles from './index.less';
+import map from './map';
+
+const FormItem = Form.Item;
+
+function generator({ defaultProps, defaultRules, type }) {
+  return (WrappedComponent) => {
+    return class BasicComponent extends Component {
+      static contextTypes = {
+        form: PropTypes.object,
+        updateActive: PropTypes.func,
+      };
+      constructor(props) {
+        super(props);
+        this.state = {
+          count: 0,
+        };
+      }
+      componentDidMount() {
+        if (this.context.updateActive) {
+          this.context.updateActive(this.props.name);
+        }
+      }
+      componentWillUnmount() {
+        clearInterval(this.interval);
+      }
+      onGetCaptcha = () => {
+        let count = 59;
+        this.setState({ count });
+        if (this.props.onGetCaptcha) {
+          this.props.onGetCaptcha();
+        }
+        this.interval = setInterval(() => {
+          count -= 1;
+          this.setState({ count });
+          if (count === 0) {
+            clearInterval(this.interval);
+          }
+        }, 1000);
+      }
+      render() {
+        const { getFieldDecorator } = this.context.form;
+        const options = {};
+        let otherProps = {};
+        const { onChange, defaultValue, rules, name, ...restProps } = this.props;
+        const { count } = this.state;
+        options.rules = rules || defaultRules;
+        if (onChange) {
+          options.onChange = onChange;
+        }
+        if (defaultValue) {
+          options.initialValue = defaultValue;
+        }
+        otherProps = restProps || otherProps;
+        if (type === 'Captcha') {
+          const inputProps = omit(otherProps, ['onGetCaptcha']);
+          return (
+            <FormItem>
+              <Row gutter={8}>
+                <Col span={16}>
+                  {getFieldDecorator(name, options)(
+                    <WrappedComponent {...defaultProps} {...inputProps} />
+                  )}
+                </Col>
+                <Col span={8}>
+                  <Button
+                    disabled={count}
+                    className={styles.getCaptcha}
+                    size="large"
+                    onClick={this.onGetCaptcha}
+                  >
+                    {count ? `${count} s` : '获取验证码'}
+                  </Button>
+                </Col>
+              </Row>
+            </FormItem>
+          );
+        }
+        return (
+          <FormItem>
+            {getFieldDecorator(name, options)(
+              <WrappedComponent {...defaultProps} {...otherProps} />
+            )}
+          </FormItem>
+        );
+      }
+    };
+  };
+}
+
+const LoginItem = {};
+Object.keys(map).forEach((item) => {
+  LoginItem[item] = generator({
+    defaultProps: map[item].props,
+    defaultRules: map[item].rules,
+    type: item,
+  })(map[item].component);
+});
+
+export default LoginItem;

+ 15 - 0
src/components/Login/LoginSubmit.js

@@ -0,0 +1,15 @@
+import React from 'react';
+import classNames from 'classnames';
+import { Button, Form } from 'antd';
+import styles from './index.less';
+
+const FormItem = Form.Item;
+
+export default ({ className, ...rest }) => {
+  const clsString = classNames(styles.submit, className);
+  return (
+    <FormItem>
+      <Button size="large" className={clsString} type="primary" htmlType="submit" {...rest} />
+    </FormItem>
+  );
+};

+ 32 - 0
src/components/Login/LoginTab.js

@@ -0,0 +1,32 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { Tabs } from 'antd';
+
+const { TabPane } = Tabs;
+
+const generateId = (() => {
+  let i = 0;
+  return (prefix: string = '') => {
+    i += 1;
+    return `${prefix}${i}`;
+  };
+})();
+
+export default class LoginTab extends Component {
+  static __ANT_PRO_LOGIN_TAB = true;
+  static contextTypes = {
+    tabUtil: PropTypes.object,
+  };
+  constructor(props) {
+    super(props);
+    this.uniqueId = generateId('login-tab-');
+  }
+  componentWillMount() {
+    if (this.context.tabUtil) {
+      this.context.tabUtil.addTab(this.uniqueId);
+    }
+  }
+  render() {
+    return <TabPane {...this.props} />;
+  }
+}

+ 113 - 0
src/components/Login/demo/basic.md

@@ -0,0 +1,113 @@
+---
+order: 0
+title: Standard Login
+---
+
+支持账号密码及手机号登录两种模式。
+
+````jsx
+import Login from 'ant-design-pro/lib/Login';
+import { Alert, Checkbox } from 'antd';
+
+const { Tab, UserName, Password, Mobile, Captcha, Submit } = Login;
+
+class LoginDemo extends React.Component {
+  state = {
+    notice: '',
+    type: 'tab2',
+    autoLogin: true,
+  }
+  onSubmit = (err, values) => {
+    console.log(`value collected ->`, {...values, autoLogin: this.state.autoLogin});
+    if (this.state.type === 'tab1') {
+      this.setState({
+        notice: '',
+      }, () => {
+        if (!err && (values.username !== 'admin' || values.password !== '888888')) {
+          setTimeout(() => {
+            this.setState({
+              notice: '账号或密码错误!',
+            })
+          }, 500);
+        }
+      })
+    }
+  }
+  onTabChange = (key) => {
+    this.setState({
+      type: key,
+    })
+  }
+  changeAutoLogin = (e) => {
+    this.setState({
+      autoLogin: e.target.checked,
+    })
+  }
+  render() {
+    return (
+      <Login
+        defaultActiveKey={this.state.type}
+        onTabChange={this.onTabChange}
+        onSubmit={this.onSubmit}
+      >
+        <Tab key="tab1" tab="账号密码登录">
+          {
+            this.state.notice &&
+            <Alert style={{ marginBottom: 24 }} message={this.state.notice} type="error" showIcon closable />
+          }
+          <UserName name="username" />
+          <Password name="password" />
+        </Tab>
+        <Tab key="tab2" tab="手机号登录">
+          <Mobile name="mobile" />
+          <Captcha onGetCaptcha={() => console.log('Get captcha!')} name="captcha" />
+        </Tab>
+        <div>
+          <Checkbox checked={this.state.autoLogin} onChange={this.changeAutoLogin}>自动登录</Checkbox>
+          <a style={{ float: 'right' }} href="">忘记密码</a>
+        </div>
+        <Submit>登录</Submit>
+        <div>
+          其他登录方式
+          <span className="icon icon-alipay" />
+          <span className="icon icon-taobao" />
+          <span className="icon icon-weibo" />
+          <a style={{ float: 'right' }} href="">注册账户</a>
+        </div>
+      </Login>
+    )
+  }
+}
+
+ReactDOM.render(<LoginDemo />, mountNode);
+````
+
+<style>
+#scaffold-src-components-Login-demo-basic .icon {
+  display: inline-block;
+  width: 24px;
+  height: 24px;
+  background: url('https://gw.alipayobjects.com/zos/rmsportal/itDzjUnkelhQNsycranf.svg');
+  margin-left: 16px;
+  vertical-align: middle;
+  cursor: pointer;
+}
+#scaffold-src-components-Login-demo-basic .icon-alipay {
+  background-position: -24px 0;
+}
+#scaffold-src-components-Login-demo-basic .icon-alipay:hover {
+  background-position: 0 0;
+}
+#scaffold-src-components-Login-demo-basic .icon-taobao {
+  background-position: -24px -24px;
+}
+#scaffold-src-components-Login-demo-basic .icon-taobao:hover {
+  background-position: 0 -24px;
+}
+#scaffold-src-components-Login-demo-basic .icon-weibo {
+  background-position: -24px -48px;
+}
+#scaffold-src-components-Login-demo-basic .icon-weibo:hover {
+  background-position: 0 -48px;
+}
+</style>

+ 121 - 0
src/components/Login/index.js

@@ -0,0 +1,121 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { Form, Tabs } from 'antd';
+import classNames from 'classnames';
+import LoginItem from './LoginItem';
+import LoginTab from './LoginTab';
+import LoginSubmit from './LoginSubmit';
+import styles from './index.less';
+
+@Form.create()
+class Login extends Component {
+  static defaultProps = {
+    className: '',
+    defaultActiveKey: '',
+    onTabChange: () => {},
+    onSubmit: () => {},
+  };
+  static propTypes = {
+    className: PropTypes.string,
+    defaultActiveKey: PropTypes.string,
+    onTabChange: PropTypes.func,
+    onSubmit: PropTypes.func,
+  };
+  static childContextTypes = {
+    tabUtil: PropTypes.object,
+    form: PropTypes.object,
+    updateActive: PropTypes.func,
+  };
+  state = {
+    type: this.props.defaultActiveKey,
+    tabs: [],
+    active: {},
+  };
+  getChildContext() {
+    return {
+      tabUtil: {
+        addTab: (id) => {
+          this.setState({
+            tabs: [...this.state.tabs, id],
+          });
+        },
+        removeTab: (id) => {
+          this.setState({
+            tabs: this.state.tabs.filter(currentId => currentId !== id),
+          });
+        },
+      },
+      form: this.props.form,
+      updateActive: (activeItem) => {
+        const { type, active } = this.state;
+        if (active[type]) {
+          active[type].push(activeItem);
+        } else {
+          active[type] = [activeItem];
+        }
+        this.setState({
+          active,
+        });
+      },
+    };
+  }
+  onSwitch = (type) => {
+    this.setState({
+      type,
+    });
+    this.props.onTabChange(type);
+  }
+  handleSubmit = (e) => {
+    e.preventDefault();
+    const { active, type } = this.state;
+    const activeFileds = active[type];
+    this.props.form.validateFields(activeFileds, { force: true },
+      (err, values) => {
+        this.props.onSubmit(err, values);
+      }
+    );
+  }
+  render() {
+    const { className, children } = this.props;
+    const { type, tabs } = this.state;
+    const TabChildren = [];
+    const otherChildren = [];
+    React.Children.forEach(children, (item) => {
+      // eslint-disable-next-line
+      if (item.type.__ANT_PRO_LOGIN_TAB) {
+        TabChildren.push(item);
+      } else {
+        otherChildren.push(item);
+      }
+    });
+    return (
+      <div className={classNames(className, styles.main)}>
+        <Form onSubmit={this.handleSubmit}>
+          {
+            tabs.length ? (
+              <div>
+                <Tabs
+                  animated={false}
+                  className={styles.tabs}
+                  activeKey={type}
+                  onChange={this.onSwitch}
+                >
+                  { TabChildren }
+                </Tabs>
+                { otherChildren }
+              </div>
+              ) : children
+          }
+        </Form>
+      </div>
+    );
+  }
+}
+
+Login.Tab = LoginTab;
+Login.Submit = LoginSubmit;
+Object.keys(LoginItem).forEach((item) => {
+  Login[item] = LoginItem[item];
+});
+
+export default Login;

+ 47 - 0
src/components/Login/index.less

@@ -0,0 +1,47 @@
+@import "~antd/lib/style/themes/default.less";
+
+.main {
+  width: 368px;
+  margin: 0 auto;
+
+  .tabs {
+    padding: 0 2px;
+    margin: 0 -2px;
+    :global {
+      .ant-tabs-tab {
+        font-size: 16px;
+        line-height: 24px;
+      }
+      .ant-input-affix-wrapper .ant-input:not(:first-child) {
+        padding-left: 34px;
+      }
+    }
+  }
+
+  :global {
+    .ant-tabs .ant-tabs-bar {
+      border-bottom: 0;
+      margin-bottom: 24px;
+      text-align: center;
+    }
+
+    .ant-form-item {
+      margin-bottom: 24px;
+    }
+  }
+
+  .prefixIcon {
+    font-size: @font-size-base;
+    color: @disabled-color;
+  }
+
+  .getCaptcha {
+    display: block;
+    width: 100%;
+  }
+
+  .submit {
+    width: 100%;
+    margin-top: 24px;
+  }
+}

+ 51 - 0
src/components/Login/index.md

@@ -0,0 +1,51 @@
+---
+title:
+  en-US: Login
+  zh-CN: Login
+subtitle: 登录
+cols: 1
+order: 15
+---
+
+支持多种登录方式切换,内置了几种常见的登录控件,可以灵活组合,也支持和自定义控件配合使用。
+
+## API
+
+### Login
+
+参数 | 说明 | 类型 | 默认值
+----|------|-----|------
+defaultActiveKey | 默认激活 tab 面板的 key | String | -
+onTabChange | 切换页签时的回调 | (key) => void | -
+onSubmit | 点击提交时的回调 | (err, values) => void | -
+
+### Login.Tab
+
+参数 | 说明 | 类型 | 默认值
+----|------|-----|------
+key | 对应选项卡的 key | String | -
+tab | 选项卡头显示文字 | ReactNode | -
+
+### Login.UserName
+
+参数 | 说明 | 类型 | 默认值
+----|------|-----|------
+name | 控件标记,提交数据中同样以此为 key | String | -
+rules | 校验规则,同 Form getFieldDecorator(id, options) 中 [option.rules 的规则](getFieldDecorator(id, options)) | object[] | -
+
+除上述属性以外,Login.UserName 还支持 antd.Input 的所有属性,并且自带默认的基础配置,包括 `placeholder` `size` `prefix` 等,这些基础配置均可被覆盖。
+
+### Login.Password、Login.Mobile 同 Login.UserName
+
+### Login.Captcha 
+
+参数 | 说明 | 类型 | 默认值
+----|------|-----|------
+onGetCaptcha | 点击获取校验码的回调 | () => void | -
+
+除上述属性以外,Login.Captcha 支持的属性与 Login.UserName 相同。
+
+### Login.Submit
+
+支持 antd.Button 的所有属性。
+

+ 55 - 0
src/components/Login/map.js

@@ -0,0 +1,55 @@
+import React from 'react';
+import { Input, Icon } from 'antd';
+import styles from './index.less';
+
+const map = {
+  UserName: {
+    component: Input,
+    props: {
+      size: 'large',
+      prefix: <Icon type="user" className={styles.prefixIcon} />,
+      placeholder: 'admin',
+    },
+    rules: [{
+      required: true, message: '请输入账户名!',
+    }],
+  },
+  Password: {
+    component: Input,
+    props: {
+      size: 'large',
+      prefix: <Icon type="lock" className={styles.prefixIcon} />,
+      type: 'password',
+      placeholder: '888888',
+    },
+    rules: [{
+      required: true, message: '请输入密码!',
+    }],
+  },
+  Mobile: {
+    component: Input,
+    props: {
+      size: 'large',
+      prefix: <Icon type="mobile" className={styles.prefixIcon} />,
+      placeholder: '手机号',
+    },
+    rules: [{
+      required: true, message: '请输入手机号!',
+    }, {
+      pattern: /^1\d{10}$/, message: '手机号格式错误!',
+    }],
+  },
+  Captcha: {
+    component: Input,
+    props: {
+      size: 'large',
+      prefix: <Icon type="mail" className={styles.prefixIcon} />,
+      placeholder: '验证码',
+    },
+    rules: [{
+      required: true, message: '请输入验证码!',
+    }],
+  },
+};
+
+export default map;

+ 63 - 155
src/routes/User/Login.js

@@ -1,185 +1,93 @@
 import React, { Component } from 'react';
 import { connect } from 'dva';
 import { Link } from 'dva/router';
-import { Form, Input, Tabs, Button, Icon, Checkbox, Row, Col, Alert } from 'antd';
+import { Checkbox, Alert, Icon } from 'antd';
+import Login from '../../components/Login';
 import styles from './Login.less';
 
-const FormItem = Form.Item;
-const { TabPane } = Tabs;
+const { Tab, UserName, Password, Mobile, Captcha, Submit } = Login;
 
 @connect(state => ({
   login: state.login,
 }))
-@Form.create()
-export default class Login extends Component {
+export default class LoginPage extends Component {
   state = {
-    count: 0,
     type: 'account',
+    autoLogin: true,
   }
 
-  componentWillUnmount() {
-    clearInterval(this.interval);
-  }
-
-  onSwitch = (type) => {
+  onTabChange = (type) => {
     this.setState({ type });
   }
 
-  onGetCaptcha = () => {
-    let count = 59;
-    this.setState({ count });
-    this.interval = setInterval(() => {
-      count -= 1;
-      this.setState({ count });
-      if (count === 0) {
-        clearInterval(this.interval);
-      }
-    }, 1000);
+  handleSubmit = (err, values) => {
+    const { type } = this.state;
+    if (!err) {
+      this.props.dispatch({
+        type: 'login/login',
+        payload: {
+          ...values,
+          type,
+        },
+      });
+    }
   }
 
-  handleSubmit = (e) => {
-    e.preventDefault();
-    this.props.form.validateFields({ force: true },
-      (err, values) => {
-        if (!err) {
-          this.props.dispatch({
-            type: 'login/login',
-            payload: {
-              ...values,
-              type: this.state.type,
-            },
-          });
-        }
-      }
-    );
+  changeAutoLogin = (e) => {
+    this.setState({
+      autoLogin: e.target.checked,
+    });
   }
 
-  renderMessage = (message) => {
+  renderMessage = (content) => {
     return (
-      <Alert
-        style={{ marginBottom: 24 }}
-        message={message}
-        type="error"
-        showIcon
-      />
+      <Alert style={{ marginBottom: 24 }} message={content} type="error" showIcon closable />
     );
   }
 
   render() {
-    const { form, login } = this.props;
-    const { getFieldDecorator } = form;
-    const { count, type } = this.state;
+    const { login } = this.props;
+    const { type } = this.state;
     return (
       <div className={styles.main}>
-        <Form onSubmit={this.handleSubmit}>
-          <Tabs animated={false} className={styles.tabs} activeKey={type} onChange={this.onSwitch}>
-            <TabPane tab="账户密码登录" key="account">
-              {
-                login.status === 'error' &&
-                login.type === 'account' &&
-                login.submitting === false &&
-                this.renderMessage('账户或密码错误')
-              }
-              <FormItem>
-                {getFieldDecorator('userName', {
-                  rules: [{
-                    required: type === 'account', message: '请输入账户名!',
-                  }],
-                })(
-                  <Input
-                    size="large"
-                    prefix={<Icon type="user" className={styles.prefixIcon} />}
-                    placeholder="admin"
-                  />
-                )}
-              </FormItem>
-              <FormItem>
-                {getFieldDecorator('password', {
-                  rules: [{
-                    required: type === 'account', message: '请输入密码!',
-                  }],
-                })(
-                  <Input
-                    size="large"
-                    prefix={<Icon type="lock" className={styles.prefixIcon} />}
-                    type="password"
-                    placeholder="888888"
-                  />
-                )}
-              </FormItem>
-            </TabPane>
-            <TabPane tab="手机号登录" key="mobile">
-              {
-                login.status === 'error' &&
-                login.type === 'mobile' &&
-                login.submitting === false &&
-                this.renderMessage('验证码错误')
-              }
-              <FormItem>
-                {getFieldDecorator('mobile', {
-                  rules: [{
-                    required: type === 'mobile', message: '请输入手机号!',
-                  }, {
-                    pattern: /^1\d{10}$/, message: '手机号格式错误!',
-                  }],
-                })(
-                  <Input
-                    size="large"
-                    prefix={<Icon type="mobile" className={styles.prefixIcon} />}
-                    placeholder="手机号"
-                  />
-                )}
-              </FormItem>
-              <FormItem>
-                <Row gutter={8}>
-                  <Col span={16}>
-                    {getFieldDecorator('captcha', {
-                      rules: [{
-                        required: type === 'mobile', message: '请输入验证码!',
-                      }],
-                    })(
-                      <Input
-                        size="large"
-                        prefix={<Icon type="mail" className={styles.prefixIcon} />}
-                        placeholder="验证码"
-                      />
-                    )}
-                  </Col>
-                  <Col span={8}>
-                    <Button
-                      disabled={count}
-                      className={styles.getCaptcha}
-                      size="large"
-                      onClick={this.onGetCaptcha}
-                    >
-                      {count ? `${count} s` : '获取验证码'}
-                    </Button>
-                  </Col>
-                </Row>
-              </FormItem>
-            </TabPane>
-          </Tabs>
-          <FormItem className={styles.additional}>
-            {getFieldDecorator('remember', {
-              valuePropName: 'checked',
-              initialValue: true,
-            })(
-              <Checkbox className={styles.autoLogin}>自动登录</Checkbox>
-            )}
-            <a className={styles.forgot} href="">忘记密码</a>
-            <Button size="large" loading={login.submitting} className={styles.submit} type="primary" htmlType="submit">
-              登录
-            </Button>
-          </FormItem>
-        </Form>
-        <div className={styles.other}>
-          其他登录方式
-          {/* 需要加到 Icon 中 */}
-          <span className={styles.iconAlipay} />
-          <span className={styles.iconTaobao} />
-          <span className={styles.iconWeibo} />
-          <Link className={styles.register} to="/user/register">注册账户</Link>
-        </div>
+        <Login
+          defaultActiveKey={type}
+          onTabChange={this.onTabChange}
+          onSubmit={this.handleSubmit}
+        >
+          <Tab key="account" tab="账户密码登录">
+            {
+              login.status === 'error' &&
+              login.type === 'account' &&
+              login.submitting === false &&
+              this.renderMessage('账户或密码错误')
+            }
+            <UserName name="userName" />
+            <Password name="password" />
+          </Tab>
+          <Tab key="mobile" tab="手机号登录">
+            {
+              login.status === 'error' &&
+              login.type === 'mobile' &&
+              login.submitting === false &&
+              this.renderMessage('验证码错误')
+            }
+            <Mobile name="mobile" />
+            <Captcha name="captcha" />
+          </Tab>
+          <div>
+            <Checkbox checked={this.state.autoLogin} onChange={this.changeAutoLogin}>自动登录</Checkbox>
+            <a style={{ float: 'right' }} href="">忘记密码</a>
+          </div>
+          <Submit>登录</Submit>
+          <div className={styles.other}>
+            其他登录方式
+            <Icon className={styles.icon} type="alipay-circle" />
+            <Icon className={styles.icon} type="taobao-circle" />
+            <Icon className={styles.icon} type="weibo-circle" />
+            <Link className={styles.register} to="/user/register">注册账户</Link>
+          </div>
+        </Login>
       </div>
     );
   }

+ 5 - 81
src/routes/User/Login.less

@@ -4,92 +4,16 @@
   width: 368px;
   margin: 0 auto;
 
-  .tabs {
-    padding: 0 2px;
-    margin: 0 -2px;
-    :global {
-      .ant-tabs-tab {
-        font-size: 16px;
-        line-height: 24px;
-      }
-      .ant-input-affix-wrapper .ant-input:not(:first-child) {
-        padding-left: 34px;
-      }
-    }
-  }
-
-  :global {
-    .ant-tabs .ant-tabs-bar {
-      border-bottom: 0;
-      margin-bottom: 24px;
-      text-align: center;
-    }
-
-    .ant-form-item {
-      margin-bottom: 24px;
-    }
-  }
-
-  .prefixIcon {
-    font-size: @font-size-base;
-    color: @disabled-color;
-  }
-
-  .getCaptcha {
-    display: block;
-    width: 100%;
-  }
-
-  .additional {
-    text-align: left;
-
-    .forgot {
-      float: right;
-    }
-
-    .submit {
-      width: 100%;
-      margin-top: 24px;
-    }
-
-    :global {
-      .ant-form-item-control {
-        line-height: 22px;
-      }
-    }
-  }
-
-  .iconAlipay, .iconTaobao, .iconWeibo {
-    display: inline-block;
-    width: 24px;
-    height: 24px;
-    background: url('https://gw.alipayobjects.com/zos/rmsportal/itDzjUnkelhQNsycranf.svg');
+  .icon {
+    font-size: 24px;
+    color: rgba(0, 0, 0, 0.2);
     margin-left: 16px;
     vertical-align: middle;
     cursor: pointer;
-  }
-
-  .iconAlipay {
-    background-position: -24px 0;
-
-    &:hover {
-      background-position: 0 0;
-    }
-  }
-
-  .iconTaobao {
-    background-position: -24px -24px;
-
-    &:hover {
-      background-position: 0 -24px;
-    }
-  }
-
-  .iconWeibo {
-    background-position: -24px -48px;
 
     &:hover {
-      background-position: 0 -48px;
+      color: @primary-color;
+      transition: color .3s;
     }
   }