✨ 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
|
.vscode
|
||||||
yarn.lock
|
yarn.lock
|
||||||
.idea
|
.idea
|
||||||
|
public/files
|
||||||
|
@ -6,10 +6,11 @@ import {Provider} from "react-redux";
|
|||||||
import ClipboardJS from "clipboard";
|
import ClipboardJS from "clipboard";
|
||||||
import store from "./redux";
|
import store from "./redux";
|
||||||
import NotFound from "./pages/public/notFound/NotFound";
|
import NotFound from "./pages/public/notFound/NotFound";
|
||||||
import UserSpace from './pages/userSpace';
|
import UserSpace from "./pages/userSpace";
|
||||||
|
|
||||||
import Login from "./pages/public/Login";
|
import Login from "./pages/public/Login";
|
||||||
import RegisterOrReset from "./pages/public/RegisterOrReset";
|
import RegisterOrReset from "./pages/public/RegisterOrReset";
|
||||||
|
import EmailVerify from "./pages/public/EmailVerify";
|
||||||
|
|
||||||
import ManageOverview from "./pages/manage/OverView";
|
import ManageOverview from "./pages/manage/OverView";
|
||||||
|
|
||||||
@ -41,7 +42,6 @@ class App extends Component {
|
|||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
<div className="fullScreen" style={mainStyle}>
|
<div className="fullScreen" style={mainStyle}>
|
||||||
<Switch>
|
<Switch>
|
||||||
|
|
||||||
{/*书签管理页面*/}
|
{/*书签管理页面*/}
|
||||||
<Redirect exact path="/" to="/manage/overview" />
|
<Redirect exact path="/" to="/manage/overview" />
|
||||||
<Route exact path="/manage/overview" component={ManageOverview} />
|
<Route exact path="/manage/overview" component={ManageOverview} />
|
||||||
@ -52,6 +52,7 @@ class App extends Component {
|
|||||||
<Route exact path="/public/login" component={Login} />
|
<Route exact path="/public/login" component={Login} />
|
||||||
<Route exact path="/public/register" component={RegisterOrReset} />
|
<Route exact path="/public/register" component={RegisterOrReset} />
|
||||||
<Route exact path="/public/resetPassword" component={RegisterOrReset} />
|
<Route exact path="/public/resetPassword" component={RegisterOrReset} />
|
||||||
|
<Route exact path="/public/verifyEmail" component={EmailVerify} />
|
||||||
<Route exact path="/404" component={NotFound} />
|
<Route exact path="/404" component={NotFound} />
|
||||||
{/* 当前面的路由都匹配不到时就会重定向到/404 */}
|
{/* 当前面的路由都匹配不到时就会重定向到/404 */}
|
||||||
<Redirect path="/" to="/404" />
|
<Redirect path="/" to="/404" />
|
||||||
|
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 React from "react";
|
||||||
import { Input, Select } from "antd";
|
import { Input, Select, Empty } from "antd";
|
||||||
import styles from "./index.module.less";
|
import styles from "./index.module.less";
|
||||||
import httpUtil from "../../util/httpUtil";
|
import httpUtil from "../../util/httpUtil";
|
||||||
|
|
||||||
@ -46,7 +46,7 @@ class Search extends React.Component {
|
|||||||
this.timer = setTimeout(() => {
|
this.timer = setTimeout(() => {
|
||||||
this.search(content);
|
this.search(content);
|
||||||
this.clearTimer();
|
this.clearTimer();
|
||||||
}, 300);
|
}, 200);
|
||||||
}
|
}
|
||||||
|
|
||||||
clearTimer() {
|
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() {
|
render() {
|
||||||
const { content, resultList, currentIndex, options, currentOptionIndex } = this.state;
|
const { content, options, currentOptionIndex } = this.state;
|
||||||
const prefix = (
|
const prefix = (
|
||||||
<Select value={options[currentOptionIndex]} onChange={this.valueIndexChange.bind(this)}>
|
<Select value={options[currentOptionIndex]} onChange={this.valueIndexChange.bind(this)}>
|
||||||
{options.map((item, index) => (
|
{options.map((item, index) => (
|
||||||
@ -143,22 +171,9 @@ class Search extends React.Component {
|
|||||||
onChange={this.contentChange.bind(this)}
|
onChange={this.contentChange.bind(this)}
|
||||||
onKeyDown={this.keyUp.bind(this)}
|
onKeyDown={this.keyUp.bind(this)}
|
||||||
onFocus={() => this.setState({ isFocus: true })}
|
onFocus={() => this.setState({ isFocus: true })}
|
||||||
onBlur={() => this.setState({ isFocus: false })}
|
onBlur={() => setTimeout(() => this.setState({ isFocus: false }), 200)}
|
||||||
/>
|
/>
|
||||||
{resultList.length > 0 ? (
|
{this.renderResults()}
|
||||||
<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}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,10 @@
|
|||||||
background: white;
|
background: white;
|
||||||
z-index: 1000;
|
z-index: 1000;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: 1px solid black;
|
border: 1px solid #f2f4f5;
|
||||||
border-top: 0;
|
border-top: 0;
|
||||||
|
padding-top: 1em;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -19,19 +21,6 @@
|
|||||||
vertical-align: baseline;
|
vertical-align: baseline;
|
||||||
}
|
}
|
||||||
|
|
||||||
// .item > span {
|
|
||||||
// display: inline-block;
|
|
||||||
// padding-right: 0.5em;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// .item:first-child {
|
|
||||||
// flex: 1;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// .item:last-child {
|
|
||||||
// flex: 2;
|
|
||||||
// }
|
|
||||||
|
|
||||||
.item:hover {
|
.item:hover {
|
||||||
background: #f2f4f5;
|
background: #f2f4f5;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
body {
|
body {
|
||||||
margin: 0;
|
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;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
}
|
}
|
||||||
@ -13,6 +14,7 @@ html,
|
|||||||
body {
|
body {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
background: #f6f6f6;
|
||||||
font-family: "lucida grande", "lucida sans unicode", lucida, helvetica, "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;
|
font-family: "lucida grande", "lucida sans unicode", lucida, helvetica, "Hiragino Sans GB", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;
|
||||||
}
|
}
|
||||||
.fullScreen {
|
.fullScreen {
|
||||||
@ -32,3 +34,7 @@ body {
|
|||||||
.across-center {
|
.across-center {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pointer{
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
@ -20,32 +20,37 @@ function mapDispatchToProps(dispatch) {
|
|||||||
class MainLayout extends React.Component {
|
class MainLayout extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
console.log(props);
|
|
||||||
this.state = {};
|
this.state = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillMount() {
|
async componentWillMount() {
|
||||||
httpUtil.get("/user/currentUserInfo").then(res => this.props.changeUserInfo(res));
|
if (!this.props.username) {
|
||||||
|
let res = await httpUtil.get("/user/currentUserInfo");
|
||||||
|
this.props.changeUserInfo(res);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
renderUserArea() {
|
renderUserArea() {
|
||||||
const { userInfo } = this.props;
|
const { username, icon } = this.props;
|
||||||
const menu = (
|
const menu = (
|
||||||
<Menu onClick={this.onClick}>
|
<Menu onClick={this.onClick}>
|
||||||
<Menu.Item key="personSpace">个人资料</Menu.Item>
|
<Menu.Item key="personSpace">个人资料</Menu.Item>
|
||||||
<Menu.Item key="logout">退出登陆</Menu.Item>
|
<Menu.Item key="logout">退出登陆</Menu.Item>
|
||||||
</Menu>
|
</Menu>
|
||||||
);
|
);
|
||||||
if (userInfo !== null) {
|
if (username != null) {
|
||||||
return (
|
return (
|
||||||
<Dropdown overlay={menu} placement="bottomCenter" trigger={["hover", "click"]}>
|
<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>
|
</Dropdown>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Link to="/public/login">登陆</Link>
|
<Link to="/public/login">登陆</Link> 
|
||||||
<Link to="/public/register">注册</Link>
|
<Link to="/public/register">注册</Link>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -69,7 +74,7 @@ class MainLayout extends React.Component {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className={"fullScreen " + styles.main}>
|
<div className={styles.main}>
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<a href="/">
|
<a href="/">
|
||||||
<img style={{ width: "1.5rem" }} src="/img/bookmarkLogo.png" alt="logo" />
|
<img style={{ width: "1.5rem" }} src="/img/bookmarkLogo.png" alt="logo" />
|
||||||
@ -77,15 +82,15 @@ class MainLayout extends React.Component {
|
|||||||
{this.renderUserArea()}
|
{this.renderUserArea()}
|
||||||
</div>
|
</div>
|
||||||
<Divider style={{ margin: 0 }} />
|
<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>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withRouter(
|
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(MainLayout));
|
||||||
connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(MainLayout)
|
|
||||||
);
|
|
||||||
|
@ -8,7 +8,14 @@
|
|||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 0.1rem 0.5rem;
|
padding: 0.1rem 0.5rem;
|
||||||
|
height: 0.5rem;
|
||||||
background-color: @mainContentColor;
|
background-color: @mainContentColor;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
width: 0.4rem;
|
||||||
|
height: 0.4rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.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 React from "react";
|
||||||
import styles from "./index.module.less";
|
import styles from "./index.module.less";
|
||||||
import MainLayout from "../../layout/MainLayout";
|
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 {
|
class UserSpace extends React.Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {};
|
this.state = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { icon, username } = this.props;
|
||||||
return (
|
return (
|
||||||
<MainLayout>
|
<MainLayout>
|
||||||
<div className={styles.main}>你好</div>
|
<UserInfo />
|
||||||
</MainLayout>
|
</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");
|
let token = localStorage.getItem("token");
|
||||||
window.token = token;
|
window.token = token;
|
||||||
return {
|
return {
|
||||||
token,
|
token
|
||||||
userInfo: null
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,12 +29,19 @@ export const CHANGE_USER_INFO = "changeUserInfo";
|
|||||||
export const changeUserInfo = userInfo => {
|
export const changeUserInfo = userInfo => {
|
||||||
return {
|
return {
|
||||||
type: CHANGE_USER_INFO,
|
type: CHANGE_USER_INFO,
|
||||||
data: {
|
data: Object.assign({}, userInfo)
|
||||||
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";
|
export const LOGOUT = "logout";
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ const LoginStatusReducer = (state = loginAction.getInitData(), action) => {
|
|||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
case loginAction.CHANGE_TOKEN:
|
case loginAction.CHANGE_TOKEN:
|
||||||
case loginAction.CHANGE_USER_INFO:
|
case loginAction.CHANGE_USER_INFO:
|
||||||
|
case loginAction.UPDATE_ONE:
|
||||||
case loginAction.LOGOUT:
|
case loginAction.LOGOUT:
|
||||||
return { ...state, ...action.data };
|
return { ...state, ...action.data };
|
||||||
default:
|
default:
|
||||||
|
@ -4,7 +4,7 @@ import axios from "axios";
|
|||||||
//定义http实例
|
//定义http实例
|
||||||
const instance = axios.create({
|
const instance = axios.create({
|
||||||
baseURL: "/bookmark/api",
|
baseURL: "/bookmark/api",
|
||||||
timeout: 15000
|
timeout: 60000
|
||||||
});
|
});
|
||||||
|
|
||||||
//实例添加请求拦截器
|
//实例添加请求拦截器
|
||||||
@ -28,7 +28,7 @@ instance.interceptors.response.use(
|
|||||||
message.error(data.message);
|
message.error(data.message);
|
||||||
return Promise.reject(data.message);
|
return Promise.reject(data.message);
|
||||||
} else {
|
} else {
|
||||||
showError(data);
|
showError(data, res.config.url.replace(res.config.baseURL, ""));
|
||||||
return Promise.reject(data.message);
|
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,
|
let description,
|
||||||
message = "出问题啦";
|
message = "出问题啦";
|
||||||
if (response) {
|
if (response) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user