diff --git a/src/App.js b/src/App.js
index 3480074..600feb6 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,5 +1,5 @@
import './App.css';
-import { Redirect, Route, SingleRouter } from './components/SingleRouter/SingleRouter';
+import { Route, SingleRouter } from './components/SingleRouter/SingleRouter';
import { AppContainer } from './index/index';
import { UploadContainer } from './upload/upload';
import { LogInContainer } from './login/login';
@@ -12,13 +12,13 @@ class App extends Component {
super(props);
this.state = {
name: '',
- role: -1
+ role: 0
};
this.setUserData = this.setUserData.bind(this);
}
- setUserData({ name, role }) {
- this.setState({ name, role });
+ setUserData({ name, role, avatar }) {
+ this.setState({ name, role, avatar });
}
render() {
@@ -29,15 +29,10 @@ class App extends Component {
-
);
}
}
-function CheckLogIn() {
- return localStorage.getItem('jwt') ? null : ;
-}
-
export default App;
diff --git a/src/admin/review/review.js b/src/admin/review/review.js
index 944e376..e1c1b1c 100644
--- a/src/admin/review/review.js
+++ b/src/admin/review/review.js
@@ -67,7 +67,7 @@ export class ReviewContainer extends Component {
-
+
{
diff --git a/src/components/AvatarUnit/AvatarUnit.css b/src/components/AvatarUnit/AvatarUnit.css
new file mode 100644
index 0000000..37d1d78
--- /dev/null
+++ b/src/components/AvatarUnit/AvatarUnit.css
@@ -0,0 +1,122 @@
+.user-avatar {
+ width: 40px;
+ height: 40px;
+ position: relative;
+}
+
+.user-avatar > img {
+ border-radius: 50%;
+ width: 100%;
+ height: 100%;
+ cursor: pointer;
+}
+
+.user-avatar-edit {
+ border-radius: 8px;
+ background-color: #f7f7f7;
+ border: 1px solid #e5e5e5;
+ width: 160px;
+ height: 26px;
+ position: absolute;
+ margin-top: 10px;
+ left: -60px;
+ filter: opacity(0);
+ transition-property: width, height, filter;
+ transition-duration: .3s;
+ transition-timing-function: ease-out;
+}
+
+.user-avatar-edit > .pointer {
+ position: absolute;
+ width: 20px;
+ height: 10px;
+ top: -10px;
+ left: 70px;
+}
+.user-avatar-edit > .pointer > svg {
+ position: absolute;
+}
+
+.user-avatar-edit-tip {
+ width: 160px;
+ height: 26px;
+ line-height: 26px;
+ padding-left: 5px;
+ box-sizing: border-box;
+ z-index: 1;
+}
+
+.user-avatar-edit.expand {
+ width: 160px;
+ height: 160px;
+}
+
+.user-avatar-edit-tip,
+.avatar-upload > * {
+ position: absolute;
+ filter: opacity(0);
+ transition: filter .3s ease-out;
+}
+.user-avatar-edit-tip.show,
+.avatar-upload > .show,
+.user-avatar-edit.show {
+ filter: opacity(1);
+}
+
+.user-avatar-edit-content {
+ overflow: hidden;
+ height: 100%;
+}
+.avatar-upload {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ height: 160px;
+}
+.avatar-upload .upload-unit-progress.show {
+ z-index: 2;
+}
+.avatar-upload .upload-unit-progress > .progress-detail {
+ color: black;
+}
+.upload-unit-progress > .icon {
+ width: 40px;
+ height: 40px;
+}
+
+.avatar-preview {
+ width: 160px;
+ height: 160px;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ background-size: cover;
+ background-position: center;
+}
+.avatar-preview.show {
+ z-index: 1;
+}
+.avatar-preview > .upload-btns {
+ margin: 0;
+ bottom: 10px;
+ position: absolute;
+}
+
+.btn-close {
+ position: absolute;
+ right: 3px;
+ top: 3px;
+ z-index: 2;
+ width: 20px;
+ height: 20px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background-color: #e5e5e5bb;
+ border-radius: 4px;
+ backdrop-filter: blur(10px);
+}
+.btn-close > svg {
+ width: 16px;
+ height: 16px;
+}
\ No newline at end of file
diff --git a/src/components/AvatarUnit/AvatarUnit.js b/src/components/AvatarUnit/AvatarUnit.js
new file mode 100644
index 0000000..0219817
--- /dev/null
+++ b/src/components/AvatarUnit/AvatarUnit.js
@@ -0,0 +1,135 @@
+import { useState, useCallback, useRef } from "react";
+import Spinner from '../Spinner/Spinner';
+import './AvatarUnit.css';
+import { multiFormPost } from '../../helper/axios';
+import { alert } from '../../helper/alert';
+import { apis } from "../../helper/apis";
+
+export default function AvatarUnit({ avatar, onChangeAvatar, showTip }) {
+ const [showBubble, setShowBubble] = useState(showTip);
+ const [showUpload, setShowUpload] = useState(false);
+ const [loading, setLoading] = useState(false);
+ const [loaded, setLoaded] = useState(false);
+ const [uploading, setUploading] = useState(false);
+ const [uploaded, setUploaded] = useState(false);
+ const [dataURL, setDataURL] = useState(null);
+ const [file, setFile] = useState(null);
+
+ const inputRef = useRef(null);
+
+ // useEffect(() => {
+ // setShowBubble(!localStorage.getItem("avatarTipShown"));
+ // localStorage.setItem("avatarTipShown", true);
+ // }, []);
+
+ const handleClose = () => {
+ setShowBubble(false);
+ setShowUpload(false);
+ setLoading(false);
+ setLoaded(false);
+ setUploading(false);
+ setUploaded(false);
+ setFile(null);
+ };
+
+ const handleFileChange = useCallback(file => {
+ if (file) {
+ if (!["image/jpeg", "image/png", "image/gif"].includes(file.type)) {
+ alert('请不要上传jpg、png、gif格式以外的文件!');
+ return;
+ }
+ setLoading(true);
+ setFile(file);
+ const reader = new FileReader();
+ reader.onload = () => {
+ setDataURL(reader.result);
+ if (inputRef.current) {
+ inputRef.current.value = "";
+ }
+ setLoaded(true);
+ setLoading(false);
+ };
+ reader.readAsDataURL(file);
+ }
+ }, []);
+
+ const handleCancel = useCallback(() => {
+ setLoaded(false);
+ setFile(null);
+ }, []);
+
+ const handleUpload = useCallback(() => {
+ if (file) {
+ setUploading(true);
+ const formData = new FormData();
+ formData.append("image", file);
+ multiFormPost(apis.uploadAvatar, formData).then(data => {
+ if (data.networkStatus === 200 && data.status) {
+ setUploaded(true);
+ setUploading(false);
+ setTimeout(() => {
+ handleClose();
+ onChangeAvatar(data.data);
+ }, 800);
+ }
+ else {
+ alert(data.data);
+ setUploading(false);
+ }
+ });
+ }
+ }, [file, onChangeAvatar]);
+
+ return (
+
+

{
+ setShowBubble(true);
+ setShowUpload(true);
+ }} />
+
+
+
+
+
点击图标修改头像
+
+
+
+
+
+ {loading ? '加载中' : uploading ? '上传中' : ''}
+
+
+
+
+
handleFileChange(e.target.files[0])} ref={inputRef} />
+
+
添加图片
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/components/UploadUnit/upload.css b/src/components/UploadUnit/upload.css
index 5f93ba0..6d1e39e 100644
--- a/src/components/UploadUnit/upload.css
+++ b/src/components/UploadUnit/upload.css
@@ -56,6 +56,8 @@
color: white;
font-size: 16px;
margin-top: 10px;
+ width: 100px;
+ text-align: center;
}
.progress-circle {
width: 50px;
diff --git a/src/components/UserControl/UserControl.js b/src/components/UserControl/UserControl.js
index f6ed8dc..338b535 100644
--- a/src/components/UserControl/UserControl.js
+++ b/src/components/UserControl/UserControl.js
@@ -1,3 +1,5 @@
+import { useEffect, useContext, Fragment } from 'react';
+
import './userControl.css';
import { images } from '../../resources.json';
import { UserContext } from '../../helper/Context';
@@ -6,56 +8,89 @@ import { get } from '../../helper/axios';
import { apis } from '../../helper/apis';
import { alert } from '../../helper/alert';
import History from '../../helper/history';
+import AvatarUnit from '../AvatarUnit/AvatarUnit';
+
+const exitContent = ['登录', '退出', '退出审核'];
+// page level: 0: everyone, 1: login needed, 2: admin only
+export default function UserControl({ pageAuthLevel, buttonOnly }) {
+ const { userData, setUserData } = useContext(UserContext);
+ useEffect(() => {
+ if (userData.role === -1) return;
+ if (userData.role === 0 && localStorage.getItem('jwt')) {
+ // 没有用户信息,但是有登录信息
+ setUserData({ role: -1, name: '加载中' });
+ get(apis.getProfile).then(({ data, status, networkStatus }) => {
+ if (networkStatus !== 200) return;
+ if (!status) return alert('获取用户信息失败:' + data + ',请稍候刷新再试');
+ setUserData({
+ name: data.realName,
+ role: data.role,
+ avatar: data.avatar,
+ });
+ });
+ return;
+ }
+
+ if (userData.role < pageAuthLevel) {
+ // 用户权限不足,依照页面权限跳转
+ if (userData.role === 1) {
+ History.force('/');
+ alert('您没有权限访问该页面,退出登录后可以使用有权限的账号登录');
+ return;
+ }
+ History.force('/login');
+ }
+
+ }, [pageAuthLevel, setUserData, userData]);
-export default function UserControl(props) {
return (
{({ userData, setUserData }) => (
- userData.role === 2
- ? (
-
-
-
- )
- : (
-
- {
- userData.role === -1
- ? (
-
- )
- : (
-
-

-
- )
- }
-
{userData.name || "加载中"}
- {
- (() => {
- if (userData.role === 1 && History.getHref().match(/^\/admin.*/))
- History.force('/');
- if (userData.role !== -1 || !localStorage.getItem('jwt')) return null;
- get(apis.getProfile).then(({ data, status, networkStatus }) => {
- if (networkStatus !== 200) return;
- if (!status) return alert('获取用户信息失败:' + data + ',请稍候刷新再试');
- setUserData({
- name: data.realName,
- role: data.role
- });
- });
- return null;
- })()
- }
-
- )
+
+ {buttonOnly || (
+
+ {
+ userData.role === -1
+ ? (
+
+ )
+ : userData.role > 0 && (
+ setUserData({ ...userData, avatar: url })}
+ showTip={!userData.avatar}
+ />
+ )
+ }
+ {
+ userData.role !== 0 && (
+ {userData.name}
+ )
+ }
+
+ )}
+ {
+ userData.role === pageAuthLevel
+ ? (
+
+ )
+ : userData.role === 2 && (
+
+ )
+ }
+
)}
);
diff --git a/src/components/UserControl/userControl.css b/src/components/UserControl/userControl.css
index b014fe7..c8a2b63 100644
--- a/src/components/UserControl/userControl.css
+++ b/src/components/UserControl/userControl.css
@@ -4,16 +4,5 @@
height: 50px;
line-height: 50px;
color: white;
- margin-left: 10px;
+ margin: 0 10px;
}
-
-.user-avatar {
- width: 40px;
- height: 40px;
-}
-
-.user-avatar > img {
- border-radius: 50%;
- width: 100%;
- height: 100%;
-}
\ No newline at end of file
diff --git a/src/helper/apis.js b/src/helper/apis.js
index d57654c..fb8b34e 100644
--- a/src/helper/apis.js
+++ b/src/helper/apis.js
@@ -4,6 +4,7 @@ export const apis = {
login: backEndBaseURL + "/user/login",
getProfile: backEndBaseURL + "/user/me",
+ uploadAvatar: backEndBaseURL + "/user/uploadAvatar",
submitMessage: backEndBaseURL + "/post/submit",
listEssence: backEndBaseURL + "/post/listPublished?page=1",
diff --git a/src/helper/axios.js b/src/helper/axios.js
index b145ac1..1f326c9 100644
--- a/src/helper/axios.js
+++ b/src/helper/axios.js
@@ -31,6 +31,20 @@ export function post(url, data, evoker) {
}
);
}
+export function multiFormPost(url, data, evoker) {
+ return send(
+ axios.post(url, data, {
+ headers: {
+ Authorization: localStorage.getItem('jwt'),
+ "Allow-Control-Allow-Origin": "*"
+ }
+ }),
+ {
+ fetcher: () => multiFormPost(url, data, evoker),
+ identifier: 'mfPost:' + url + (evoker ? `@${evoker}` : '')
+ }
+ );
+}
const waitToSend = [];
diff --git a/src/index/index.css b/src/index/index.css
index ec2874f..eab8d65 100644
--- a/src/index/index.css
+++ b/src/index/index.css
@@ -1,7 +1,7 @@
.poster {
width: 100%;
height: 400px;
- background-color: #80010a;
+ background-color: #84000B;
display: flex;
flex-direction: column;
align-items: center;
@@ -10,10 +10,15 @@
.img-poster {
height: 100%;
}
-.btn-partIn {
+.index-btns {
position: absolute;
right: 40px;
bottom: 20px;
+ display: flex;
+ height: 40px;
+}
+.index-btns > .user {
+ margin-right: 10px;
}
/* 这里的卡片和分栏结构就比较接近bootstrap了,可惜视觉做的太古板,如果愿意的话改成Material Design就更好了 */
.split-lg > .card {
diff --git a/src/index/index.js b/src/index/index.js
index 2fc84f3..17972c8 100644
--- a/src/index/index.js
+++ b/src/index/index.js
@@ -1,6 +1,7 @@
import { Component } from 'react';
import { Link } from '../components/SingleRouter/SingleRouter';
import Spinner from '../components/Spinner/Spinner';
+import UserControl from '../components/UserControl/UserControl';
import { alert } from '../helper/alert';
import { apis } from '../helper/apis';
import { get } from '../helper/axios';
@@ -38,7 +39,10 @@ export class AppContainer extends Component {

-
点击参加
+
+
+ 点击参加
+
diff --git a/src/upload/upload.js b/src/upload/upload.js
index 7237802..aa53dd3 100644
--- a/src/upload/upload.js
+++ b/src/upload/upload.js
@@ -63,7 +63,7 @@ export class UploadContainer extends Component {
-
+