初始化 antd-pro

This commit is contained in:
sin
2019-02-27 11:06:55 +08:00
parent 9f4fdb7f6e
commit b2068ae44b
458 changed files with 28090 additions and 2 deletions

View File

@@ -0,0 +1,88 @@
import React, { PureComponent } from 'react';
import { List, Card, Icon, Dropdown, Menu, Avatar, Tooltip } from 'antd';
import numeral from 'numeral';
import { connect } from 'dva';
import { formatWan } from '@/utils/utils';
import stylesApplications from '../../List/Applications.less';
@connect(({ list }) => ({
list,
}))
class Center extends PureComponent {
render() {
const {
list: { 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 = ({ 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>
)}
/>
);
}
}
export default Center;

View File

@@ -0,0 +1,59 @@
import React, { PureComponent } from 'react';
import { List, Icon, Tag } from 'antd';
import { connect } from 'dva';
import ArticleListContent from '@/components/ArticleListContent';
import styles from './Articles.less';
@connect(({ list }) => ({
list,
}))
class Center extends PureComponent {
render() {
const {
list: { list },
} = this.props;
const IconText = ({ type, text }) => (
<span>
<Icon type={type} style={{ marginRight: 8 }} />
{text}
</span>
);
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={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 Center;

View File

@@ -0,0 +1,12 @@
@import '~antd/lib/style/themes/default.less';
.articleList {
:global {
.ant-list-item:first-child {
padding-top: 0;
}
}
}
a.listItemMetaTitle {
color: @heading-color;
}

View File

@@ -0,0 +1,216 @@
import React, { PureComponent } from 'react';
import { connect } from 'dva';
import Link from 'umi/link';
import router from 'umi/router';
import { Card, Row, Col, Icon, Avatar, Tag, Divider, Spin, Input } from 'antd';
import GridContent from '@/components/PageHeaderWrapper/GridContent';
import styles from './Center.less';
@connect(({ loading, user, project }) => ({
listLoading: loading.effects['list/fetch'],
currentUser: user.currentUser,
currentUserLoading: loading.effects['user/fetchCurrent'],
project,
projectLoading: loading.effects['project/fetchNotice'],
}))
class Center extends PureComponent {
state = {
newTags: [],
inputVisible: false,
inputValue: '',
};
componentDidMount() {
const { dispatch } = this.props;
dispatch({
type: 'user/fetchCurrent',
});
dispatch({
type: 'list/fetch',
payload: {
count: 8,
},
});
dispatch({
type: 'project/fetchNotice',
});
}
onTabChange = key => {
const { match } = this.props;
switch (key) {
case 'articles':
router.push(`${match.url}/articles`);
break;
case 'applications':
router.push(`${match.url}/applications`);
break;
case 'projects':
router.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 {
listLoading,
currentUser,
currentUserLoading,
project: { notice },
projectLoading,
match,
location,
children,
} = this.props;
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 (
<GridContent 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}
>
{children}
</Card>
</Col>
</Row>
</GridContent>
);
}
}
export default Center;

View File

@@ -0,0 +1,97 @@
@import '~antd/lib/style/themes/default.less';
@import '~@/utils/utils.less';
.avatarHolder {
margin-bottom: 24px;
text-align: center;
& > img {
width: 104px;
height: 104px;
margin-bottom: 20px;
}
.name {
margin-bottom: 4px;
color: @heading-color;
font-weight: 500;
font-size: 20px;
line-height: 28px;
}
}
.detail {
p {
position: relative;
margin-bottom: 8px;
padding-left: 26px;
&:last-child {
margin-bottom: 0;
}
}
i {
position: absolute;
top: 4px;
left: 0;
width: 14px;
height: 14px;
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 {
margin-bottom: 12px;
color: @heading-color;
font-weight: 500;
}
.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 0.3s;
.textOverflow();
&:hover {
color: @primary-color;
}
}
}
.tabsCard {
:global {
.ant-card-head {
padding: 0 16px;
}
}
}

View File

@@ -0,0 +1,52 @@
import React, { PureComponent } from 'react';
import { List, Card } from 'antd';
import moment from 'moment';
import { connect } from 'dva';
import AvatarList from '@/components/AvatarList';
import stylesProjects from '../../List/Projects.less';
@connect(({ list }) => ({
list,
}))
class Center extends PureComponent {
render() {
const {
list: { list },
} = this.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>{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>
)}
/>
);
}
}
export default Center;

View File

@@ -0,0 +1,192 @@
import React, { Component, Fragment } from 'react';
import { formatMessage, FormattedMessage } from 'umi/locale';
import { Form, Input, Upload, Select, Button } from 'antd';
import { connect } from 'dva';
import styles from './BaseView.less';
import GeographicView from './GeographicView';
import PhoneView from './PhoneView';
// import { getTimeDistance } from '@/utils/utils';
const FormItem = Form.Item;
const { Option } = Select;
// 头像组件 方便以后独立,增加裁剪之类的功能
const AvatarView = ({ avatar }) => (
<Fragment>
<div className={styles.avatar_title}>
<FormattedMessage id="app.settings.basic.avatar" defaultMessage="Avatar" />
</div>
<div className={styles.avatar}>
<img src={avatar} alt="avatar" />
</div>
<Upload fileList={[]}>
<div className={styles.button_view}>
<Button icon="upload">
<FormattedMessage id="app.settings.basic.change-avatar" defaultMessage="Change avatar" />
</Button>
</div>
</Upload>
</Fragment>
);
const validatorGeographic = (rule, value, callback) => {
const { province, city } = value;
if (!province.key) {
callback('Please input your province!');
}
if (!city.key) {
callback('Please input your city!');
}
callback();
};
const validatorPhone = (rule, value, callback) => {
const values = value.split('-');
if (!values[0]) {
callback('Please input your area code!');
}
if (!values[1]) {
callback('Please input your phone number!');
}
callback();
};
@connect(({ user }) => ({
currentUser: user.currentUser,
}))
@Form.create()
class BaseView extends Component {
componentDidMount() {
this.setBaseInfo();
}
setBaseInfo = () => {
const { currentUser, form } = this.props;
Object.keys(form.getFieldsValue()).forEach(key => {
const obj = {};
obj[key] = currentUser[key] || null;
form.setFieldsValue(obj);
});
};
getAvatarURL() {
const { currentUser } = this.props;
if (currentUser.avatar) {
return currentUser.avatar;
}
const url = 'https://gw.alipayobjects.com/zos/antfincdn/XAosXuNZyF/BiazfanxmamNRoxxVxka.png';
return url;
}
getViewDom = ref => {
this.view = ref;
};
render() {
const {
form: { getFieldDecorator },
} = this.props;
return (
<div className={styles.baseView} ref={this.getViewDom}>
<div className={styles.left}>
<Form layout="vertical" onSubmit={this.handleSubmit} hideRequiredMark>
<FormItem label={formatMessage({ id: 'app.settings.basic.email' })}>
{getFieldDecorator('email', {
rules: [
{
required: true,
message: formatMessage({ id: 'app.settings.basic.email-message' }, {}),
},
],
})(<Input />)}
</FormItem>
<FormItem label={formatMessage({ id: 'app.settings.basic.nickname' })}>
{getFieldDecorator('name', {
rules: [
{
required: true,
message: formatMessage({ id: 'app.settings.basic.nickname-message' }, {}),
},
],
})(<Input />)}
</FormItem>
<FormItem label={formatMessage({ id: 'app.settings.basic.profile' })}>
{getFieldDecorator('profile', {
rules: [
{
required: true,
message: formatMessage({ id: 'app.settings.basic.profile-message' }, {}),
},
],
})(
<Input.TextArea
placeholder={formatMessage({ id: 'app.settings.basic.profile-placeholder' })}
rows={4}
/>
)}
</FormItem>
<FormItem label={formatMessage({ id: 'app.settings.basic.country' })}>
{getFieldDecorator('country', {
rules: [
{
required: true,
message: formatMessage({ id: 'app.settings.basic.country-message' }, {}),
},
],
})(
<Select style={{ maxWidth: 220 }}>
<Option value="China">中国</Option>
</Select>
)}
</FormItem>
<FormItem label={formatMessage({ id: 'app.settings.basic.geographic' })}>
{getFieldDecorator('geographic', {
rules: [
{
required: true,
message: formatMessage({ id: 'app.settings.basic.geographic-message' }, {}),
},
{
validator: validatorGeographic,
},
],
})(<GeographicView />)}
</FormItem>
<FormItem label={formatMessage({ id: 'app.settings.basic.address' })}>
{getFieldDecorator('address', {
rules: [
{
required: true,
message: formatMessage({ id: 'app.settings.basic.address-message' }, {}),
},
],
})(<Input />)}
</FormItem>
<FormItem label={formatMessage({ id: 'app.settings.basic.phone' })}>
{getFieldDecorator('phone', {
rules: [
{
required: true,
message: formatMessage({ id: 'app.settings.basic.phone-message' }, {}),
},
{ validator: validatorPhone },
],
})(<PhoneView />)}
</FormItem>
<Button type="primary">
<FormattedMessage
id="app.settings.basic.update"
defaultMessage="Update Information"
/>
</Button>
</Form>
</div>
<div className={styles.right}>
<AvatarView avatar={this.getAvatarURL()} />
</div>
</div>
);
}
}
export default BaseView;

View File

@@ -0,0 +1,52 @@
@import '~antd/lib/style/themes/default.less';
.baseView {
display: flex;
padding-top: 12px;
.left {
min-width: 224px;
max-width: 448px;
}
.right {
flex: 1;
padding-left: 104px;
.avatar_title {
height: 22px;
margin-bottom: 8px;
color: @heading-color;
font-size: @font-size-base;
line-height: 22px;
}
.avatar {
width: 144px;
height: 144px;
margin-bottom: 12px;
overflow: hidden;
img {
width: 100%;
}
}
.button_view {
width: 144px;
text-align: center;
}
}
}
@media screen and (max-width: @screen-xl) {
.baseView {
flex-direction: column-reverse;
.right {
display: flex;
flex-direction: column;
align-items: center;
max-width: 448px;
padding: 20px;
.avatar_title {
display: none;
}
}
}
}

View File

@@ -0,0 +1,60 @@
import React, { Component, Fragment } from 'react';
import { formatMessage, FormattedMessage } from 'umi/locale';
import { Icon, List } from 'antd';
class BindingView extends Component {
getData = () => [
{
title: formatMessage({ id: 'app.settings.binding.taobao' }, {}),
description: formatMessage({ id: 'app.settings.binding.taobao-description' }, {}),
actions: [
<a>
<FormattedMessage id="app.settings.binding.bind" defaultMessage="Bind" />
</a>,
],
avatar: <Icon type="taobao" className="taobao" />,
},
{
title: formatMessage({ id: 'app.settings.binding.alipay' }, {}),
description: formatMessage({ id: 'app.settings.binding.alipay-description' }, {}),
actions: [
<a>
<FormattedMessage id="app.settings.binding.bind" defaultMessage="Bind" />
</a>,
],
avatar: <Icon type="alipay" className="alipay" />,
},
{
title: formatMessage({ id: 'app.settings.binding.dingding' }, {}),
description: formatMessage({ id: 'app.settings.binding.dingding-description' }, {}),
actions: [
<a>
<FormattedMessage id="app.settings.binding.bind" defaultMessage="Bind" />
</a>,
],
avatar: <Icon type="dingding" className="dingding" />,
},
];
render() {
return (
<Fragment>
<List
itemLayout="horizontal"
dataSource={this.getData()}
renderItem={item => (
<List.Item actions={item.actions}>
<List.Item.Meta
avatar={item.avatar}
title={item.title}
description={item.description}
/>
</List.Item>
)}
/>
</Fragment>
);
}
}
export default BindingView;

View File

@@ -0,0 +1,128 @@
import React, { PureComponent } from 'react';
import { Select, Spin } from 'antd';
import { connect } from 'dva';
import styles from './GeographicView.less';
const { Option } = Select;
const nullSlectItem = {
label: '',
key: '',
};
@connect(({ geographic }) => {
const { province, isLoading, city } = geographic;
return {
province,
city,
isLoading,
};
})
class GeographicView extends PureComponent {
componentDidMount = () => {
const { dispatch } = this.props;
dispatch({
type: 'geographic/fetchProvince',
});
};
componentDidUpdate(props) {
const { dispatch, value } = this.props;
if (!props.value && !!value && !!value.province) {
dispatch({
type: 'geographic/fetchCity',
payload: value.province.key,
});
}
}
getProvinceOption() {
const { province } = this.props;
return this.getOption(province);
}
getCityOption = () => {
const { city } = this.props;
return this.getOption(city);
};
getOption = list => {
if (!list || list.length < 1) {
return (
<Option key={0} value={0}>
没有找到选项
</Option>
);
}
return list.map(item => (
<Option key={item.id} value={item.id}>
{item.name}
</Option>
));
};
selectProvinceItem = item => {
const { dispatch, onChange } = this.props;
dispatch({
type: 'geographic/fetchCity',
payload: item.key,
});
onChange({
province: item,
city: nullSlectItem,
});
};
selectCityItem = item => {
const { value, onChange } = this.props;
onChange({
province: value.province,
city: item,
});
};
conversionObject() {
const { value } = this.props;
if (!value) {
return {
province: nullSlectItem,
city: nullSlectItem,
};
}
const { province, city } = value;
return {
province: province || nullSlectItem,
city: city || nullSlectItem,
};
}
render() {
const { province, city } = this.conversionObject();
const { isLoading } = this.props;
return (
<Spin spinning={isLoading} wrapperClassName={styles.row}>
<Select
className={styles.item}
value={province}
labelInValue
showSearch
onSelect={this.selectProvinceItem}
>
{this.getProvinceOption()}
</Select>
<Select
className={styles.item}
value={city}
labelInValue
showSearch
onSelect={this.selectCityItem}
>
{this.getCityOption()}
</Select>
</Spin>
);
}
}
export default GeographicView;

View File

@@ -0,0 +1,19 @@
@import '~antd/lib/style/themes/default.less';
.row {
.item {
width: 50%;
max-width: 220px;
}
.item:first-child {
width: ~'calc(50% - 8px)';
margin-right: 8px;
}
}
@media screen and (max-width: @screen-sm) {
.item:first-child {
margin: 0;
margin-bottom: 8px;
}
}

View File

@@ -0,0 +1,125 @@
import React, { Component } from 'react';
import { connect } from 'dva';
import router from 'umi/router';
import { FormattedMessage } from 'umi/locale';
import { Menu } from 'antd';
import GridContent from '@/components/PageHeaderWrapper/GridContent';
import styles from './Info.less';
const { Item } = Menu;
@connect(({ user }) => ({
currentUser: user.currentUser,
}))
class Info extends Component {
constructor(props) {
super(props);
const { match, location } = props;
const menuMap = {
base: <FormattedMessage id="app.settings.menuMap.basic" defaultMessage="Basic Settings" />,
security: (
<FormattedMessage id="app.settings.menuMap.security" defaultMessage="Security Settings" />
),
binding: (
<FormattedMessage id="app.settings.menuMap.binding" defaultMessage="Account Binding" />
),
notification: (
<FormattedMessage
id="app.settings.menuMap.notification"
defaultMessage="New Message Notification"
/>
),
};
const key = location.pathname.replace(`${match.path}/`, '');
this.state = {
mode: 'inline',
menuMap,
selectKey: menuMap[key] ? key : 'base',
};
}
static getDerivedStateFromProps(props, state) {
const { match, location } = props;
let selectKey = location.pathname.replace(`${match.path}/`, '');
selectKey = state.menuMap[selectKey] ? selectKey : 'base';
if (selectKey !== state.selectKey) {
return { selectKey };
}
return null;
}
componentDidMount() {
window.addEventListener('resize', this.resize);
this.resize();
}
componentWillUnmount() {
window.removeEventListener('resize', this.resize);
}
getmenu = () => {
const { menuMap } = this.state;
return Object.keys(menuMap).map(item => <Item key={item}>{menuMap[item]}</Item>);
};
getRightTitle = () => {
const { selectKey, menuMap } = this.state;
return menuMap[selectKey];
};
selectKey = ({ key }) => {
router.push(`/account/settings/${key}`);
this.setState({
selectKey: key,
});
};
resize = () => {
if (!this.main) {
return;
}
requestAnimationFrame(() => {
let mode = 'inline';
const { offsetWidth } = this.main;
if (this.main.offsetWidth < 641 && offsetWidth > 400) {
mode = 'horizontal';
}
if (window.innerWidth < 768 && offsetWidth > 400) {
mode = 'horizontal';
}
this.setState({
mode,
});
});
};
render() {
const { children, currentUser } = this.props;
if (!currentUser.userid) {
return '';
}
const { mode, selectKey } = this.state;
return (
<GridContent>
<div
className={styles.main}
ref={ref => {
this.main = ref;
}}
>
<div className={styles.leftmenu}>
<Menu mode={mode} selectedKeys={[selectKey]} onClick={this.selectKey}>
{this.getmenu()}
</Menu>
</div>
<div className={styles.right}>
<div className={styles.title}>{this.getRightTitle()}</div>
{children}
</div>
</div>
</GridContent>
);
}
}
export default Info;

View File

@@ -0,0 +1,97 @@
@import '~antd/lib/style/themes/default.less';
.main {
display: flex;
width: 100%;
height: 100%;
padding-top: 16px;
padding-bottom: 16px;
overflow: auto;
background-color: @body-background;
.leftmenu {
width: 224px;
border-right: @border-width-base @border-style-base @border-color-split;
:global {
.ant-menu-inline {
border: none;
}
.ant-menu:not(.ant-menu-horizontal) .ant-menu-item-selected {
font-weight: bold;
}
}
}
.right {
flex: 1;
padding-top: 8px;
padding-right: 40px;
padding-bottom: 8px;
padding-left: 40px;
.title {
margin-bottom: 12px;
color: @heading-color;
font-weight: 500;
font-size: 20px;
line-height: 28px;
}
}
:global {
.ant-list-split .ant-list-item:last-child {
border-bottom: 1px solid #e8e8e8;
}
.ant-list-item {
padding-top: 14px;
padding-bottom: 14px;
}
}
}
:global {
.ant-list-item-meta {
// 账号绑定图标
.taobao {
display: block;
color: #ff4000;
font-size: 48px;
line-height: 48px;
border-radius: @border-radius-base;
}
.dingding {
margin: 2px;
padding: 6px;
color: #fff;
font-size: 32px;
line-height: 32px;
background-color: #2eabff;
border-radius: @border-radius-base;
}
.alipay {
color: #2eabff;
font-size: 48px;
line-height: 48px;
border-radius: @border-radius-base;
}
}
// 密码强度
font.strong {
color: @success-color;
}
font.medium {
color: @warning-color;
}
font.weak {
color: @error-color;
}
}
@media screen and (max-width: @screen-md) {
.main {
flex-direction: column;
.leftmenu {
width: 100%;
border: none;
}
.right {
padding: 40px;
}
}
}

View File

@@ -0,0 +1,50 @@
import React, { Component, Fragment } from 'react';
import { formatMessage } from 'umi/locale';
import { Switch, List } from 'antd';
class NotificationView extends Component {
getData = () => {
const Action = (
<Switch
checkedChildren={formatMessage({ id: 'app.settings.open' })}
unCheckedChildren={formatMessage({ id: 'app.settings.close' })}
defaultChecked
/>
);
return [
{
title: formatMessage({ id: 'app.settings.notification.password' }, {}),
description: formatMessage({ id: 'app.settings.notification.password-description' }, {}),
actions: [Action],
},
{
title: formatMessage({ id: 'app.settings.notification.messages' }, {}),
description: formatMessage({ id: 'app.settings.notification.messages-description' }, {}),
actions: [Action],
},
{
title: formatMessage({ id: 'app.settings.notification.todo' }, {}),
description: formatMessage({ id: 'app.settings.notification.todo-description' }, {}),
actions: [Action],
},
];
};
render() {
return (
<Fragment>
<List
itemLayout="horizontal"
dataSource={this.getData()}
renderItem={item => (
<List.Item actions={item.actions}>
<List.Item.Meta title={item.title} description={item.description} />
</List.Item>
)}
/>
</Fragment>
);
}
}
export default NotificationView;

View File

@@ -0,0 +1,33 @@
import React, { Fragment, PureComponent } from 'react';
import { Input } from 'antd';
import styles from './PhoneView.less';
class PhoneView extends PureComponent {
render() {
const { value, onChange } = this.props;
let values = ['', ''];
if (value) {
values = value.split('-');
}
return (
<Fragment>
<Input
className={styles.area_code}
value={values[0]}
onChange={e => {
onChange(`${e.target.value}-${values[1]}`);
}}
/>
<Input
className={styles.phone_number}
onChange={e => {
onChange(`${values[0]}-${e.target.value}`);
}}
value={values[1]}
/>
</Fragment>
);
}
}
export default PhoneView;

View File

@@ -0,0 +1,11 @@
@import '~antd/lib/style/themes/default.less';
.area_code {
width: 30%;
max-width: 128px;
margin-right: 8px;
}
.phone_number {
width: ~'calc(70% - 8px)';
max-width: 312px;
}

View File

@@ -0,0 +1,102 @@
import React, { Component, Fragment } from 'react';
import { formatMessage, FormattedMessage } from 'umi/locale';
import { List } from 'antd';
// import { getTimeDistance } from '@/utils/utils';
const passwordStrength = {
strong: (
<font className="strong">
<FormattedMessage id="app.settings.security.strong" defaultMessage="Strong" />
</font>
),
medium: (
<font className="medium">
<FormattedMessage id="app.settings.security.medium" defaultMessage="Medium" />
</font>
),
weak: (
<font className="weak">
<FormattedMessage id="app.settings.security.weak" defaultMessage="Weak" />
Weak
</font>
),
};
class SecurityView extends Component {
getData = () => [
{
title: formatMessage({ id: 'app.settings.security.password' }, {}),
description: (
<Fragment>
{formatMessage({ id: 'app.settings.security.password-description' })}
{passwordStrength.strong}
</Fragment>
),
actions: [
<a>
<FormattedMessage id="app.settings.security.modify" defaultMessage="Modify" />
</a>,
],
},
{
title: formatMessage({ id: 'app.settings.security.phone' }, {}),
description: `${formatMessage(
{ id: 'app.settings.security.phone-description' },
{}
)}138****8293`,
actions: [
<a>
<FormattedMessage id="app.settings.security.modify" defaultMessage="Modify" />
</a>,
],
},
{
title: formatMessage({ id: 'app.settings.security.question' }, {}),
description: formatMessage({ id: 'app.settings.security.question-description' }, {}),
actions: [
<a>
<FormattedMessage id="app.settings.security.set" defaultMessage="Set" />
</a>,
],
},
{
title: formatMessage({ id: 'app.settings.security.email' }, {}),
description: `${formatMessage(
{ id: 'app.settings.security.email-description' },
{}
)}ant***sign.com`,
actions: [
<a>
<FormattedMessage id="app.settings.security.modify" defaultMessage="Modify" />
</a>,
],
},
{
title: formatMessage({ id: 'app.settings.security.mfa' }, {}),
description: formatMessage({ id: 'app.settings.security.mfa-description' }, {}),
actions: [
<a>
<FormattedMessage id="app.settings.security.bind" defaultMessage="Bind" />
</a>,
],
},
];
render() {
return (
<Fragment>
<List
itemLayout="horizontal"
dataSource={this.getData()}
renderItem={item => (
<List.Item actions={item.actions}>
<List.Item.Meta title={item.title} description={item.description} />
</List.Item>
)}
/>
</Fragment>
);
}
}
export default SecurityView;

View File

@@ -0,0 +1,65 @@
import { queryProvince, queryCity } from '@/services/geographic';
export default {
namespace: 'geographic',
state: {
province: [],
city: [],
isLoading: false,
},
effects: {
*fetchProvince(_, { call, put }) {
yield put({
type: 'changeLoading',
payload: true,
});
const response = yield call(queryProvince);
yield put({
type: 'setProvince',
payload: response,
});
yield put({
type: 'changeLoading',
payload: false,
});
},
*fetchCity({ payload }, { call, put }) {
yield put({
type: 'changeLoading',
payload: true,
});
const response = yield call(queryCity, payload);
yield put({
type: 'setCity',
payload: response,
});
yield put({
type: 'changeLoading',
payload: false,
});
},
},
reducers: {
setProvince(state, action) {
return {
...state,
province: action.payload,
};
},
setCity(state, action) {
return {
...state,
city: action.payload,
};
},
changeLoading(state, action) {
return {
...state,
isLoading: action.payload,
};
},
},
};