初始化 antd-pro
This commit is contained in:
88
admin-web/src/pages/Account/Center/Applications.js
Normal file
88
admin-web/src/pages/Account/Center/Applications.js
Normal 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;
|
||||
59
admin-web/src/pages/Account/Center/Articles.js
Normal file
59
admin-web/src/pages/Account/Center/Articles.js
Normal 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;
|
||||
12
admin-web/src/pages/Account/Center/Articles.less
Normal file
12
admin-web/src/pages/Account/Center/Articles.less
Normal 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;
|
||||
}
|
||||
216
admin-web/src/pages/Account/Center/Center.js
Normal file
216
admin-web/src/pages/Account/Center/Center.js
Normal 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;
|
||||
97
admin-web/src/pages/Account/Center/Center.less
Normal file
97
admin-web/src/pages/Account/Center/Center.less
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
52
admin-web/src/pages/Account/Center/Projects.js
Normal file
52
admin-web/src/pages/Account/Center/Projects.js
Normal 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;
|
||||
192
admin-web/src/pages/Account/Settings/BaseView.js
Normal file
192
admin-web/src/pages/Account/Settings/BaseView.js
Normal 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;
|
||||
52
admin-web/src/pages/Account/Settings/BaseView.less
Normal file
52
admin-web/src/pages/Account/Settings/BaseView.less
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
60
admin-web/src/pages/Account/Settings/BindingView.js
Normal file
60
admin-web/src/pages/Account/Settings/BindingView.js
Normal 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;
|
||||
128
admin-web/src/pages/Account/Settings/GeographicView.js
Normal file
128
admin-web/src/pages/Account/Settings/GeographicView.js
Normal 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;
|
||||
19
admin-web/src/pages/Account/Settings/GeographicView.less
Normal file
19
admin-web/src/pages/Account/Settings/GeographicView.less
Normal 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;
|
||||
}
|
||||
}
|
||||
125
admin-web/src/pages/Account/Settings/Info.js
Normal file
125
admin-web/src/pages/Account/Settings/Info.js
Normal 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;
|
||||
97
admin-web/src/pages/Account/Settings/Info.less
Normal file
97
admin-web/src/pages/Account/Settings/Info.less
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
50
admin-web/src/pages/Account/Settings/NotificationView.js
Normal file
50
admin-web/src/pages/Account/Settings/NotificationView.js
Normal 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;
|
||||
33
admin-web/src/pages/Account/Settings/PhoneView.js
Normal file
33
admin-web/src/pages/Account/Settings/PhoneView.js
Normal 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;
|
||||
11
admin-web/src/pages/Account/Settings/PhoneView.less
Normal file
11
admin-web/src/pages/Account/Settings/PhoneView.less
Normal 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;
|
||||
}
|
||||
102
admin-web/src/pages/Account/Settings/SecurityView.js
Normal file
102
admin-web/src/pages/Account/Settings/SecurityView.js
Normal 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;
|
||||
65
admin-web/src/pages/Account/Settings/models/geographic.js
Normal file
65
admin-web/src/pages/Account/Settings/models/geographic.js
Normal 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,
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user