+
-
{this.props.children}
+
+ {this.props.children}
+
+
);
}
}
-export default withRouter(
- connect(
- mapStateToProps,
- mapDispatchToProps
- )(MainLayout)
-);
+export default withRouter(connect(mapStateToProps, mapDispatchToProps)(MainLayout));
diff --git a/front/src/layout/MainLayout/index.module.less b/front/src/layout/MainLayout/index.module.less
index 1e3a535..68e71d3 100644
--- a/front/src/layout/MainLayout/index.module.less
+++ b/front/src/layout/MainLayout/index.module.less
@@ -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;
+ }
}
diff --git a/front/src/pages/public/EmailVerify/index.jsx b/front/src/pages/public/EmailVerify/index.jsx
new file mode 100644
index 0000000..1c24fac
--- /dev/null
+++ b/front/src/pages/public/EmailVerify/index.jsx
@@ -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 (
+
+ {this.state.message}
+
+ );
+ }
+}
+
+export default EmailVerify;
diff --git a/front/src/pages/userSpace/components/UserInfo copy/index.jsx b/front/src/pages/userSpace/components/UserInfo copy/index.jsx
new file mode 100644
index 0000000..d0c4e4c
--- /dev/null
+++ b/front/src/pages/userSpace/components/UserInfo copy/index.jsx
@@ -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 (
+
+ {/* 头像昵称 */}
+
+
+
+
+
+
{username}
+
+ {/* 个人资料 */}
+
+
+ );
+ }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(UserSpace);
diff --git a/front/src/pages/userSpace/components/UserInfo copy/index.module.less b/front/src/pages/userSpace/components/UserInfo copy/index.module.less
new file mode 100644
index 0000000..73c023d
--- /dev/null
+++ b/front/src/pages/userSpace/components/UserInfo copy/index.module.less
@@ -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;
+ }
+ }
+}
diff --git a/front/src/pages/userSpace/components/UserInfo/index.jsx b/front/src/pages/userSpace/components/UserInfo/index.jsx
new file mode 100644
index 0000000..06c3d0c
--- /dev/null
+++ b/front/src/pages/userSpace/components/UserInfo/index.jsx
@@ -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 = (
+
+ {itemKey == "password" ? (
+
+ this.setState({ [itemKey]: e.target.value })} />
+ this.setState({ repeatPassword: e.target.value })} />
+
+ ) : (
+
this.setState({ [itemKey]: e.target.value })} />
+ )}
+
+
+
+
+
+
+ );
+ } else {
+ block = (
+
+ this.setState({ [isShowKey]: true })}
+ >
+ {itemKey === "password" ? "********" : itemValue}
+
+
+ );
+ }
+ return (
+
+ {label.length > 0 ?
{label}
: null}
+ {block}
+
+ );
+ }
+
+ render() {
+ const { isModelShow } = this.state;
+ const { icon, username } = this.props;
+ return (
+
+ {/* 头像昵称 */}
+
+
+
+
+ {/* 个人信息 */}
+
+ {this.renderItem("", "username", "isUsername")}
+ {this.renderItem("邮箱", "email", "isEmail")}
+ {this.renderItem("密码", "password", "isPassword")}
+
+
+
this.setState({ isModelShow: false })} onChange={this.actionIdChange.bind(this)} />
+
+ );
+ }
+}
+
+export default connect(mapStateToProps, mapDispatchToProps)(UserInfo);
diff --git a/front/src/pages/userSpace/components/UserInfo/index.module.less b/front/src/pages/userSpace/components/UserInfo/index.module.less
new file mode 100644
index 0000000..69a5ba2
--- /dev/null
+++ b/front/src/pages/userSpace/components/UserInfo/index.module.less
@@ -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;
+ }
+ }
+ }
+}
diff --git a/front/src/pages/userSpace/index.jsx b/front/src/pages/userSpace/index.jsx
index 02b1be3..38dd53e 100644
--- a/front/src/pages/userSpace/index.jsx
+++ b/front/src/pages/userSpace/index.jsx
@@ -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 (
- 你好
+
);
}
}
-export default UserSpace;
+export default connect(mapStateToProps, mapDispatchToProps)(UserSpace);
diff --git a/front/src/pages/userSpace/index.module.less b/front/src/pages/userSpace/index.module.less
index 2183220..73c023d 100644
--- a/front/src/pages/userSpace/index.module.less
+++ b/front/src/pages/userSpace/index.module.less
@@ -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;
+ }
+ }
}
diff --git a/front/src/redux/action/LoginInfoAction.js b/front/src/redux/action/LoginInfoAction.js
index fb73854..35afa14 100644
--- a/front/src/redux/action/LoginInfoAction.js
+++ b/front/src/redux/action/LoginInfoAction.js
@@ -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";
diff --git a/front/src/redux/reducer/loginInfo.js b/front/src/redux/reducer/loginInfo.js
index ad3d39f..2258e78 100644
--- a/front/src/redux/reducer/loginInfo.js
+++ b/front/src/redux/reducer/loginInfo.js
@@ -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:
diff --git a/front/src/util/httpUtil.js b/front/src/util/httpUtil.js
index a2c563f..5ba57f4 100644
--- a/front/src/util/httpUtil.js
+++ b/front/src/util/httpUtil.js
@@ -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) {