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 { logo - +
{ 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 ( +
+ user avatar { + 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 - ? ( - - ) - : ( -
- user avatar -
- ) - } -
{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 { logo
- +