✨ Feat: [前端]:修改个人信息功能完成
This commit is contained in:
parent
0390308d44
commit
df5fb48be0
@ -0,0 +1 @@
|
||||
INSERT INTO `bookmark`.`url`(`method`, `url`, `type`) VALUES ('GET', '/baseInfo/verifyEmail', 0);
|
1
front/.gitignore
vendored
1
front/.gitignore
vendored
@ -24,3 +24,4 @@ yarn-error.log*
|
||||
.vscode
|
||||
yarn.lock
|
||||
.idea
|
||||
public/files
|
||||
|
@ -1,15 +1,16 @@
|
||||
import React, {Component} from "react";
|
||||
import {Route, Switch, Redirect} from "react-router-dom";
|
||||
import {message} from "antd";
|
||||
import {withRouter} from "react-router-dom";
|
||||
import {Provider} from "react-redux";
|
||||
import React, { Component } from "react";
|
||||
import { Route, Switch, Redirect } from "react-router-dom";
|
||||
import { message } from "antd";
|
||||
import { withRouter } from "react-router-dom";
|
||||
import { Provider } from "react-redux";
|
||||
import ClipboardJS from "clipboard";
|
||||
import store from "./redux";
|
||||
import NotFound from "./pages/public/notFound/NotFound";
|
||||
import UserSpace from './pages/userSpace';
|
||||
import UserSpace from "./pages/userSpace";
|
||||
|
||||
import Login from "./pages/public/Login";
|
||||
import RegisterOrReset from "./pages/public/RegisterOrReset";
|
||||
import EmailVerify from "./pages/public/EmailVerify";
|
||||
|
||||
import ManageOverview from "./pages/manage/OverView";
|
||||
|
||||
@ -23,11 +24,11 @@ class App extends Component {
|
||||
componentDidMount() {
|
||||
//初始化clipboard
|
||||
let clipboard = new ClipboardJS(".copy-to-board", {
|
||||
text: function (trigger) {
|
||||
text: function(trigger) {
|
||||
return window.copyUrl;
|
||||
}
|
||||
});
|
||||
clipboard.on("success", function (e) {
|
||||
clipboard.on("success", function(e) {
|
||||
message.success("复制成功");
|
||||
e.clearSelection();
|
||||
});
|
||||
@ -41,20 +42,20 @@ class App extends Component {
|
||||
<Provider store={store}>
|
||||
<div className="fullScreen" style={mainStyle}>
|
||||
<Switch>
|
||||
|
||||
{/*书签管理页面*/}
|
||||
<Redirect exact path="/" to="/manage/overview"/>
|
||||
<Route exact path="/manage/overview" component={ManageOverview}/>
|
||||
<Redirect exact path="/" to="/manage/overview" />
|
||||
<Route exact path="/manage/overview" component={ManageOverview} />
|
||||
|
||||
{/*个人中心页面*/}
|
||||
<Route exact path="/userSpace" component={UserSpace}/>
|
||||
<Route exact path="/userSpace" component={UserSpace} />
|
||||
{/* 公共页面 */}
|
||||
<Route exact path="/public/login" component={Login}/>
|
||||
<Route exact path="/public/register" component={RegisterOrReset}/>
|
||||
<Route exact path="/public/resetPassword" component={RegisterOrReset}/>
|
||||
<Route exact path="/404" component={NotFound}/>
|
||||
<Route exact path="/public/login" component={Login} />
|
||||
<Route exact path="/public/register" component={RegisterOrReset} />
|
||||
<Route exact path="/public/resetPassword" component={RegisterOrReset} />
|
||||
<Route exact path="/public/verifyEmail" component={EmailVerify} />
|
||||
<Route exact path="/404" component={NotFound} />
|
||||
{/* 当前面的路由都匹配不到时就会重定向到/404 */}
|
||||
<Redirect path="/" to="/404"/>
|
||||
<Redirect path="/" to="/404" />
|
||||
</Switch>
|
||||
</div>
|
||||
</Provider>
|
||||
|
31
front/src/components/PasswordCheck.jsx
Normal file
31
front/src/components/PasswordCheck.jsx
Normal file
@ -0,0 +1,31 @@
|
||||
import React from "react";
|
||||
import { message, Modal, Input } from "antd";
|
||||
import http from "../util/httpUtil";
|
||||
|
||||
export default class PasswordCheck extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
password: ""
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交
|
||||
*/
|
||||
async submit() {
|
||||
console.log(this.state);
|
||||
let actionId = await http.post("/user/checkPassword", { password: this.state.password });
|
||||
this.props.onChange(actionId);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { visible, onClose } = this.props;
|
||||
const { password } = this.state;
|
||||
return (
|
||||
<Modal title="密码校验" visible={visible} onOk={this.submit.bind(this)} onCancel={onClose}>
|
||||
<Input.Password placeholder="输入旧密码" value={password} onChange={e => this.setState({ password: e.target.value })} />
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import React from "react";
|
||||
import { Input, Select } from "antd";
|
||||
import { Input, Select, Empty } from "antd";
|
||||
import styles from "./index.module.less";
|
||||
import httpUtil from "../../util/httpUtil";
|
||||
|
||||
@ -46,7 +46,7 @@ class Search extends React.Component {
|
||||
this.timer = setTimeout(() => {
|
||||
this.search(content);
|
||||
this.clearTimer();
|
||||
}, 300);
|
||||
}, 200);
|
||||
}
|
||||
|
||||
clearTimer() {
|
||||
@ -119,8 +119,36 @@ class Search extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染结果列表
|
||||
*/
|
||||
renderResults() {
|
||||
const { resultList, currentIndex, currentOptionIndex, isFocus } = this.state;
|
||||
if (currentOptionIndex !== 0 || !isFocus) {
|
||||
return;
|
||||
}
|
||||
if (resultList.length > 0) {
|
||||
return (
|
||||
<div className={styles.resultList}>
|
||||
{resultList.map((item, index) => (
|
||||
<div
|
||||
className={`${styles.item} ${index === currentIndex ? styles.checked : ""}`}
|
||||
key={item.bookmarkId}
|
||||
onClick={() => window.open(item.url)}
|
||||
>
|
||||
<span style={{ fontWeight: 600 }}>{item.name} </span>
|
||||
<span style={{ fontSize: "0.8em", fontWeight: 400 }}>{item.url}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
return <Empty className={styles.resultList} image={Empty.PRESENTED_IMAGE_SIMPLE} />;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { content, resultList, currentIndex, options, currentOptionIndex } = this.state;
|
||||
const { content, options, currentOptionIndex } = this.state;
|
||||
const prefix = (
|
||||
<Select value={options[currentOptionIndex]} onChange={this.valueIndexChange.bind(this)}>
|
||||
{options.map((item, index) => (
|
||||
@ -143,22 +171,9 @@ class Search extends React.Component {
|
||||
onChange={this.contentChange.bind(this)}
|
||||
onKeyDown={this.keyUp.bind(this)}
|
||||
onFocus={() => this.setState({ isFocus: true })}
|
||||
onBlur={() => this.setState({ isFocus: false })}
|
||||
onBlur={() => setTimeout(() => this.setState({ isFocus: false }), 200)}
|
||||
/>
|
||||
{resultList.length > 0 ? (
|
||||
<div className={styles.resultList}>
|
||||
{resultList.map((item, index) => (
|
||||
<div
|
||||
className={`${styles.item} ${index === currentIndex ? styles.checked : ""}`}
|
||||
key={item.bookmarkId}
|
||||
onClick={() => window.open(item.url)}
|
||||
>
|
||||
<span style={{ fontWeight: 600 }}>{item.name} </span>
|
||||
<span style={{ fontSize: "0.8em", fontWeight: 400 }}>{item.url}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
{this.renderResults()}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -7,8 +7,10 @@
|
||||
background: white;
|
||||
z-index: 1000;
|
||||
width: 100%;
|
||||
border: 1px solid black;
|
||||
border: 1px solid #f2f4f5;
|
||||
border-top: 0;
|
||||
padding-top: 1em;
|
||||
margin: 0;
|
||||
|
||||
.item {
|
||||
cursor: pointer;
|
||||
@ -19,19 +21,6 @@
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
// .item > span {
|
||||
// display: inline-block;
|
||||
// padding-right: 0.5em;
|
||||
// }
|
||||
|
||||
// .item:first-child {
|
||||
// flex: 1;
|
||||
// }
|
||||
|
||||
// .item:last-child {
|
||||
// flex: 2;
|
||||
// }
|
||||
|
||||
.item:hover {
|
||||
background: #f2f4f5;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
|
||||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
@ -13,6 +14,7 @@ html,
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #f6f6f6;
|
||||
font-family: "lucida grande", "lucida sans unicode", lucida, helvetica, "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;
|
||||
}
|
||||
.fullScreen {
|
||||
@ -32,3 +34,7 @@ body {
|
||||
.across-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.pointer{
|
||||
cursor: pointer;
|
||||
}
|
||||
|
@ -20,32 +20,37 @@ function mapDispatchToProps(dispatch) {
|
||||
class MainLayout extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
console.log(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
componentWillMount() {
|
||||
httpUtil.get("/user/currentUserInfo").then(res => this.props.changeUserInfo(res));
|
||||
async componentWillMount() {
|
||||
if (!this.props.username) {
|
||||
let res = await httpUtil.get("/user/currentUserInfo");
|
||||
this.props.changeUserInfo(res);
|
||||
}
|
||||
}
|
||||
|
||||
renderUserArea() {
|
||||
const { userInfo } = this.props;
|
||||
const { username, icon } = this.props;
|
||||
const menu = (
|
||||
<Menu onClick={this.onClick}>
|
||||
<Menu.Item key="personSpace">个人资料</Menu.Item>
|
||||
<Menu.Item key="logout">退出登陆</Menu.Item>
|
||||
</Menu>
|
||||
);
|
||||
if (userInfo !== null) {
|
||||
if (username != null) {
|
||||
return (
|
||||
<Dropdown overlay={menu} placement="bottomCenter" trigger={["hover", "click"]}>
|
||||
<span style={{ cursor: "pointer" }}>{userInfo.username}</span>
|
||||
<span style={{ cursor: "pointer" }}>
|
||||
<img className={styles.icon} src={icon} alt="icon" />
|
||||
{username}
|
||||
</span>
|
||||
</Dropdown>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<div>
|
||||
<Link to="/public/login">登陆</Link>
|
||||
<Link to="/public/login">登陆</Link> 
|
||||
<Link to="/public/register">注册</Link>
|
||||
</div>
|
||||
);
|
||||
@ -69,7 +74,7 @@ class MainLayout extends React.Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className={"fullScreen " + styles.main}>
|
||||
<div className={styles.main}>
|
||||
<div className={styles.header}>
|
||||
<a href="/">
|
||||
<img style={{ width: "1.5rem" }} src="/img/bookmarkLogo.png" alt="logo" />
|
||||
@ -77,15 +82,15 @@ class MainLayout extends React.Component {
|
||||
{this.renderUserArea()}
|
||||
</div>
|
||||
<Divider style={{ margin: 0 }} />
|
||||
<div className={styles.content}>{this.props.children}</div>
|
||||
<div style={{ minHeight: `calc(${document.body.clientHeight}px - 1.45rem)` }} className={styles.content}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
<div className={styles.footer}>
|
||||
开源地址:<a href="https://github.com/FleyX/bookmark">github.com/FleyX/bookmark</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withRouter(
|
||||
connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(MainLayout)
|
||||
);
|
||||
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(MainLayout));
|
||||
|
@ -8,7 +8,14 @@
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.1rem 0.5rem;
|
||||
height: 0.5rem;
|
||||
background-color: @mainContentColor;
|
||||
|
||||
.icon {
|
||||
width: 0.4rem;
|
||||
height: 0.4rem;
|
||||
border-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.content {
|
||||
@ -27,4 +34,13 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
text-align: center;
|
||||
font-size: 1em;
|
||||
margin-top: 0.1rem;
|
||||
padding: 0.2rem;
|
||||
height: 0.6rem;
|
||||
background: @mainContentColor;
|
||||
}
|
||||
}
|
||||
|
42
front/src/pages/public/EmailVerify/index.jsx
Normal file
42
front/src/pages/public/EmailVerify/index.jsx
Normal file
@ -0,0 +1,42 @@
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import MainLayout from "../../../layout/MainLayout/index";
|
||||
import http from "../../../util/httpUtil";
|
||||
import queryString from "query-string";
|
||||
|
||||
const style = {
|
||||
"text-align": "center",
|
||||
"padding-top": "200px"
|
||||
};
|
||||
|
||||
class EmailVerify extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
message: "正在校验中,请稍候!"
|
||||
};
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
let param = queryString.parseUrl(window.location.href);
|
||||
try {
|
||||
await http.get(`baseInfo/verifyEmail?secret=${param.query.key}`);
|
||||
this.setState({ message: "校验成功,3s后跳转到首页" });
|
||||
setTimeout(() => {
|
||||
window.location.href = "/";
|
||||
}, 3000);
|
||||
} catch (e) {
|
||||
this.setState({ message: "校验失败" });
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<MainLayout>
|
||||
<div style={{ textAlign: "center" }}>{this.state.message}</div>
|
||||
</MainLayout>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default EmailVerify;
|
64
front/src/pages/userSpace/components/UserInfo copy/index.jsx
Normal file
64
front/src/pages/userSpace/components/UserInfo copy/index.jsx
Normal file
@ -0,0 +1,64 @@
|
||||
import React from "react";
|
||||
import { message } from "antd";
|
||||
import styles from "./index.module.less";
|
||||
import MainLayout from "../../layout/MainLayout";
|
||||
import { connect } from "react-redux";
|
||||
import * as action from "../../redux/action/LoginInfoAction";
|
||||
import httpUtil from "../../util/httpUtil";
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return state[action.DATA_NAME];
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
updateOne: (key, value) => dispatch(action.updateOne(key, value))
|
||||
};
|
||||
}
|
||||
|
||||
class UserSpace extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
async changeIcon(e) {
|
||||
let file = e.target.files[0];
|
||||
if (!file || file.size > 500 * 1024) {
|
||||
message.error("文件大小请勿超过500KB");
|
||||
return;
|
||||
}
|
||||
let formData = new FormData();
|
||||
formData.append("file", file);
|
||||
let res = await httpUtil.post("/user/icon", formData, {
|
||||
headers: { "Content-Type": "multipart/form-data" }
|
||||
});
|
||||
console.log(res);
|
||||
this.props.updateOne("icon", res);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { icon, username } = this.props;
|
||||
return (
|
||||
<MainLayout>
|
||||
{/* 头像昵称 */}
|
||||
<div className={styles.head}>
|
||||
<div className={styles.icon}>
|
||||
<img src={icon} alt="icon" className={styles.full} />
|
||||
<label className={styles.full}>
|
||||
<input type="file" style={{ display: "none" }} onChange={this.changeIcon.bind(this)} />
|
||||
<div className={styles.full + " " + styles.changeIcon}>
|
||||
<span>编辑</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
<div>{username}</div>
|
||||
</div>
|
||||
{/* 个人资料 */}
|
||||
<div></div>
|
||||
</MainLayout>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(UserSpace);
|
@ -0,0 +1,41 @@
|
||||
@import "../../global.less";
|
||||
.head {
|
||||
background: @mainContentColor;
|
||||
padding: 0.1rem;
|
||||
|
||||
display: flex;
|
||||
|
||||
.icon {
|
||||
position: relative;
|
||||
border-radius: 5px;
|
||||
@media (min-width: 768px) {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
width: 75px;
|
||||
height: 75px;
|
||||
}
|
||||
|
||||
.full {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.changeIcon {
|
||||
background-color: transparent;
|
||||
color: rgba(173, 166, 166, 0);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.changeIcon:hover {
|
||||
// background-color: rbga(173, 166, 166, 1);
|
||||
background-color: rgba(173, 166, 166, 0.8) !important;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
181
front/src/pages/userSpace/components/UserInfo/index.jsx
Normal file
181
front/src/pages/userSpace/components/UserInfo/index.jsx
Normal file
@ -0,0 +1,181 @@
|
||||
import React from "react";
|
||||
import { message, Button, Tooltip, Input, Form } from "antd";
|
||||
import styles from "./index.module.less";
|
||||
import { connect } from "react-redux";
|
||||
import * as action from "../../../../redux/action/LoginInfoAction";
|
||||
import httpUtil from "../../../../util/httpUtil";
|
||||
import PasswordCheck from "../../../../components/PasswordCheck";
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return state[action.DATA_NAME];
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
updateOne: (key, value) => dispatch(action.updateOne(key, value))
|
||||
};
|
||||
}
|
||||
|
||||
class UserInfo extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
console.log(props);
|
||||
this.state = {
|
||||
email: null,
|
||||
isEmail: false,
|
||||
username: null,
|
||||
isUsername: false,
|
||||
password: "",
|
||||
repeatPassword: "",
|
||||
isPassword: false,
|
||||
actionId: null,
|
||||
isModelShow: false,
|
||||
//当前正在提交的
|
||||
currentAction: null,
|
||||
//当前正在提交的
|
||||
currentShowKey: null
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改头像
|
||||
* @param {*} e
|
||||
*/
|
||||
async changeIcon(e) {
|
||||
let file = e.target.files[0];
|
||||
if (!file || file.size > 500 * 1024) {
|
||||
message.error("文件大小请勿超过500KB");
|
||||
return;
|
||||
}
|
||||
let formData = new FormData();
|
||||
formData.append("file", file);
|
||||
let res = await httpUtil.post("/user/icon", formData, {
|
||||
headers: { "Content-Type": "multipart/form-data" }
|
||||
});
|
||||
console.log(res);
|
||||
this.props.updateOne("icon", res);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新
|
||||
* @param {*} itemKey 修改的字段
|
||||
* @param {*} isShowKey 是否修改字段
|
||||
*/
|
||||
async submit(itemKey, isShowKey) {
|
||||
if (this.state[itemKey] == null || this.state[itemKey] == "") {
|
||||
message.error("请修改后重试");
|
||||
return;
|
||||
}
|
||||
const { password, repeatPassword, actionId, email, username } = this.state;
|
||||
if (itemKey === "password" && password !== repeatPassword) {
|
||||
message.error("两次密码不一致");
|
||||
return;
|
||||
}
|
||||
if ((itemKey === "password" || itemKey === "email") && actionId == null) {
|
||||
message.warning("敏感操作,需校验密码");
|
||||
this.setState({ isModelShow: true, currentAction: itemKey, currentShowKey: isShowKey });
|
||||
return;
|
||||
}
|
||||
try {
|
||||
if (itemKey === "password") {
|
||||
await httpUtil.post("/baseInfo/password", { actionId, password });
|
||||
message.info("密码更新成功");
|
||||
} else if (itemKey === "email") {
|
||||
await httpUtil.post("/baseInfo/email", { actionId, email });
|
||||
this.props.updateOne("newEmail", email);
|
||||
message.info("新邮箱验证邮件已发送,请注意查收");
|
||||
} else {
|
||||
await httpUtil.post("/baseInfo/username", { username });
|
||||
this.props.updateOne("username", username);
|
||||
}
|
||||
this.setState({ [isShowKey]: false });
|
||||
} finally {
|
||||
this.setState({ actionId: null });
|
||||
}
|
||||
}
|
||||
|
||||
async actionIdChange(actionId) {
|
||||
const { currentAction, currentShowKey } = this.state;
|
||||
this.setState({ actionId, isModelShow: false, currentAction: null, currentShowKey: null });
|
||||
this.submit(currentAction, currentShowKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* 渲染用户名
|
||||
* @param {string} itemKey
|
||||
* @param {string} isShowKey
|
||||
*/
|
||||
renderItem(label, itemKey, isShowKey) {
|
||||
let repeatPassword = this.state.repeatPassword;
|
||||
let itemValue = this.state[itemKey] === null ? this.props[itemKey] : this.state[itemKey];
|
||||
let isShow = this.state[isShowKey];
|
||||
let block;
|
||||
if (isShow) {
|
||||
block = (
|
||||
<div>
|
||||
{itemKey == "password" ? (
|
||||
<div>
|
||||
<Input.Password value={itemValue} placeholder="新密码" onChange={e => this.setState({ [itemKey]: e.target.value })} />
|
||||
<Input.Password value={repeatPassword} placeholder="重复密码" onChange={e => this.setState({ repeatPassword: e.target.value })} />
|
||||
</div>
|
||||
) : (
|
||||
<Input value={itemValue} onChange={e => this.setState({ [itemKey]: e.target.value })} />
|
||||
)}
|
||||
<div style={{ marginTop: "0.1rem" }}>
|
||||
<Button type="primary" onClick={this.submit.bind(this, itemKey, isShowKey)}>
|
||||
保存
|
||||
</Button>
|
||||
|
||||
<Button onClick={() => this.setState({ [isShowKey]: false })}>取消</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
block = (
|
||||
<Tooltip title="点击编辑">
|
||||
<div
|
||||
className={itemKey === "username" ? styles.username : "" + " pointer " + styles.value}
|
||||
onClick={() => this.setState({ [isShowKey]: true })}
|
||||
>
|
||||
{itemKey === "password" ? "********" : itemValue}
|
||||
</div>
|
||||
</Tooltip>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div className={styles.item + " flex"}>
|
||||
{label.length > 0 ? <div className={styles.label}>{label}</div> : null}
|
||||
{block}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { isModelShow } = this.state;
|
||||
const { icon, username } = this.props;
|
||||
return (
|
||||
<div className={styles.head}>
|
||||
{/* 头像昵称 */}
|
||||
<div className={styles.icon}>
|
||||
<img src={icon} alt="icon" className={styles.full} />
|
||||
<label className={styles.full}>
|
||||
<input type="file" style={{ display: "none" }} onChange={this.changeIcon.bind(this)} />
|
||||
<div className={styles.full + " " + styles.changeIcon}>
|
||||
<span>编辑</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
{/* 个人信息 */}
|
||||
<div className={styles.userinfo}>
|
||||
{this.renderItem("", "username", "isUsername")}
|
||||
{this.renderItem("邮箱", "email", "isEmail")}
|
||||
{this.renderItem("密码", "password", "isPassword")}
|
||||
</div>
|
||||
|
||||
<PasswordCheck visible={isModelShow} onClose={() => this.setState({ isModelShow: false })} onChange={this.actionIdChange.bind(this)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(UserInfo);
|
@ -0,0 +1,68 @@
|
||||
@import "../../../../global.less";
|
||||
.head {
|
||||
background: @mainContentColor;
|
||||
padding: 0.1rem;
|
||||
|
||||
display: flex;
|
||||
|
||||
.icon {
|
||||
position: relative;
|
||||
border-radius: 5px;
|
||||
margin-right: 1em;
|
||||
@media (min-width: 768px) {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
width: 75px;
|
||||
height: 75px;
|
||||
}
|
||||
|
||||
.full {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.changeIcon {
|
||||
background-color: transparent;
|
||||
color: rgba(173, 166, 166, 0);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.changeIcon:hover {
|
||||
// background-color: rbga(173, 166, 166, 1);
|
||||
background-color: rgba(173, 166, 166, 0.8) !important;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.userinfo {
|
||||
flex: 1;
|
||||
|
||||
.username {
|
||||
font-size: 0.4rem;
|
||||
color: black;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.item {
|
||||
font-size: 1.5em;
|
||||
padding-top: 1em;
|
||||
padding-bottom: 1em;
|
||||
border-bottom: 1px solid #ebebeb;
|
||||
|
||||
.label {
|
||||
width: 5em;
|
||||
color: black;
|
||||
}
|
||||
|
||||
.value {
|
||||
min-width: 5em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,20 +1,33 @@
|
||||
import React from "react";
|
||||
import styles from "./index.module.less";
|
||||
import MainLayout from "../../layout/MainLayout";
|
||||
import { connect } from "react-redux";
|
||||
import * as action from "../../redux/action/LoginInfoAction";
|
||||
import UserInfo from "./components/UserInfo";
|
||||
|
||||
function mapStateToProps(state) {
|
||||
return state[action.DATA_NAME];
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch) {
|
||||
return {
|
||||
updateOne: (key, value) => dispatch(action.updateOne(key, value))
|
||||
};
|
||||
}
|
||||
|
||||
class UserSpace extends React.Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { icon, username } = this.props;
|
||||
return (
|
||||
<MainLayout>
|
||||
<div className={styles.main}>你好</div>
|
||||
<UserInfo />
|
||||
</MainLayout>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default UserSpace;
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(UserSpace);
|
||||
|
@ -1,3 +1,41 @@
|
||||
.main{
|
||||
|
||||
@import "../../global.less";
|
||||
.head {
|
||||
background: @mainContentColor;
|
||||
padding: 0.1rem;
|
||||
|
||||
display: flex;
|
||||
|
||||
.icon {
|
||||
position: relative;
|
||||
border-radius: 5px;
|
||||
@media (min-width: 768px) {
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
width: 75px;
|
||||
height: 75px;
|
||||
}
|
||||
|
||||
.full {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.changeIcon {
|
||||
background-color: transparent;
|
||||
color: rgba(173, 166, 166, 0);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
font-size: 2em;
|
||||
}
|
||||
|
||||
.changeIcon:hover {
|
||||
// background-color: rbga(173, 166, 166, 1);
|
||||
background-color: rgba(173, 166, 166, 0.8) !important;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,7 @@ export function getInitData() {
|
||||
let token = localStorage.getItem("token");
|
||||
window.token = token;
|
||||
return {
|
||||
token,
|
||||
userInfo: null
|
||||
token
|
||||
};
|
||||
}
|
||||
|
||||
@ -30,12 +29,19 @@ export const CHANGE_USER_INFO = "changeUserInfo";
|
||||
export const changeUserInfo = userInfo => {
|
||||
return {
|
||||
type: CHANGE_USER_INFO,
|
||||
data: {
|
||||
userInfo
|
||||
}
|
||||
data: Object.assign({}, userInfo)
|
||||
};
|
||||
};
|
||||
|
||||
//更新一个数据
|
||||
export const UPDATE_ONE = "updateOne";
|
||||
|
||||
export const updateOne = (key, value) => {
|
||||
let data = {};
|
||||
data[key] = value;
|
||||
return { type: UPDATE_ONE, data };
|
||||
};
|
||||
|
||||
// 退出登录
|
||||
export const LOGOUT = "logout";
|
||||
|
||||
|
@ -4,6 +4,7 @@ const LoginStatusReducer = (state = loginAction.getInitData(), action) => {
|
||||
switch (action.type) {
|
||||
case loginAction.CHANGE_TOKEN:
|
||||
case loginAction.CHANGE_USER_INFO:
|
||||
case loginAction.UPDATE_ONE:
|
||||
case loginAction.LOGOUT:
|
||||
return { ...state, ...action.data };
|
||||
default:
|
||||
|
@ -4,7 +4,7 @@ import axios from "axios";
|
||||
//定义http实例
|
||||
const instance = axios.create({
|
||||
baseURL: "/bookmark/api",
|
||||
timeout: 15000
|
||||
timeout: 60000
|
||||
});
|
||||
|
||||
//实例添加请求拦截器
|
||||
@ -28,7 +28,7 @@ instance.interceptors.response.use(
|
||||
message.error(data.message);
|
||||
return Promise.reject(data.message);
|
||||
} else {
|
||||
showError(data);
|
||||
showError(data, res.config.url.replace(res.config.baseURL, ""));
|
||||
return Promise.reject(data.message);
|
||||
}
|
||||
},
|
||||
@ -39,7 +39,10 @@ instance.interceptors.response.use(
|
||||
}
|
||||
);
|
||||
|
||||
function showError(response) {
|
||||
function showError(response, url) {
|
||||
if (url === "/user/currentUserInfo") {
|
||||
return;
|
||||
}
|
||||
let description,
|
||||
message = "出问题啦";
|
||||
if (response) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user