比較提交
4 次程式碼提交
b0f33d0bf4
...
master
作者 | SHA1 | 日期 | |
---|---|---|---|
9b3c1a7b55 | |||
c80ce3d31b | |||
bbcfe3e324 | |||
a5358fef87 |
@@ -119,4 +119,33 @@
|
||||
.btn-close > svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.nickname-edit {
|
||||
margin-top: 5px;
|
||||
margin-left: -1px;
|
||||
width: 360px;
|
||||
padding: 4px;
|
||||
border-radius: 8px;
|
||||
background-color: #f7f7f7;
|
||||
border: 1px solid #e5e5e5;
|
||||
display: flex;
|
||||
}
|
||||
.nickname-edit > label {
|
||||
line-height: 1.7em;
|
||||
font-size: 18px;
|
||||
}
|
||||
.nickname-edit > input {
|
||||
flex-grow: 1;
|
||||
border: none;
|
||||
background-color: #eee;
|
||||
border-radius: 6px;
|
||||
margin-right: 10px;
|
||||
padding: 0 5px;
|
||||
font-size: 16px;
|
||||
}
|
||||
.nickname-edit > .btn {
|
||||
padding: .1em 0;
|
||||
width: 60px;
|
||||
border-radius: 6px;
|
||||
}
|
@@ -1,11 +1,11 @@
|
||||
import { useState, useCallback, useRef } from "react";
|
||||
import { useState, useCallback, useRef, useEffect } from "react";
|
||||
import Spinner from '../Spinner/Spinner';
|
||||
import './AvatarUnit.css';
|
||||
import { multiFormPost } from '../../helper/axios';
|
||||
import { multiFormPost, post } from '../../helper/axios';
|
||||
import { alert } from '../../helper/alert';
|
||||
import { apis } from "../../helper/apis";
|
||||
|
||||
export default function AvatarUnit({ avatar, onChangeAvatar, showTip }) {
|
||||
export default function AvatarUnit({ avatar, nickname, onChangeAvatar, showTip }) {
|
||||
const [showBubble, setShowBubble] = useState(showTip);
|
||||
const [showUpload, setShowUpload] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -14,9 +14,15 @@ export default function AvatarUnit({ avatar, onChangeAvatar, showTip }) {
|
||||
const [uploaded, setUploaded] = useState(false);
|
||||
const [dataURL, setDataURL] = useState(null);
|
||||
const [file, setFile] = useState(null);
|
||||
const [nicknameNow, setNicknameNow] = useState('');
|
||||
const [nicknameUploading, setNicknameUploading] = useState(false);
|
||||
|
||||
const inputRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
setNicknameNow(nickname || '');
|
||||
}, [nickname]);
|
||||
|
||||
// useEffect(() => {
|
||||
// setShowBubble(!localStorage.getItem("avatarTipShown"));
|
||||
// localStorage.setItem("avatarTipShown", true);
|
||||
@@ -64,22 +70,37 @@ export default function AvatarUnit({ avatar, onChangeAvatar, showTip }) {
|
||||
const formData = new FormData();
|
||||
formData.append("image", file);
|
||||
multiFormPost(apis.uploadAvatar, formData).then(data => {
|
||||
if (data.networkStatus === 200 && data.status) {
|
||||
setUploading(false);
|
||||
if (data.networkStatus !== 200) return;
|
||||
if (data.status) {
|
||||
setUploaded(true);
|
||||
setUploading(false);
|
||||
setTimeout(() => {
|
||||
handleClose();
|
||||
onChangeAvatar(data.data);
|
||||
}, 800);
|
||||
}
|
||||
else {
|
||||
alert(data.data);
|
||||
setUploading(false);
|
||||
alert('头像上传失败:' + data.data);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [file, onChangeAvatar]);
|
||||
|
||||
const handleChangeNickname = useCallback(() => {
|
||||
setNicknameUploading(true);
|
||||
post(apis.updateNickname, { nickname: nicknameNow }).then(data => {
|
||||
setNicknameUploading(false);
|
||||
if (data.networkStatus !== 200) return;
|
||||
if (data.status) {
|
||||
alert('昵称已修改为:' + nicknameNow);
|
||||
setNicknameNow(nicknameNow);
|
||||
}
|
||||
else {
|
||||
alert('修改失败:' + data.data.map(item => item.msg).join(','));
|
||||
}
|
||||
});
|
||||
}, [nicknameNow]);
|
||||
|
||||
return (
|
||||
<div className="user-avatar">
|
||||
<img src={avatar} alt="user avatar" onClick={() => {
|
||||
@@ -96,7 +117,7 @@ export default function AvatarUnit({ avatar, onChangeAvatar, showTip }) {
|
||||
<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M597.333333 512l284.444445 284.444444c2.929778 3.328 2.929778 22.357333 0 28.444445l-56.888889 56.888889c-6.087111 2.929778-25.116444 2.929778-28.444445 0L512 597.333333 227.555556 881.777778c-3.328 2.929778-22.328889 2.929778-28.444445 0l-56.888889-56.888889c-2.929778-6.087111-2.929778-25.116444 0-28.444445l284.444445-284.444444L142.222222 227.555556c-2.929778-3.328-2.929778-22.328889 0-28.444445l56.888889-56.888889c6.115556-2.929778 25.116444-2.929778 28.444445 0l284.444444 284.444445L796.444444 142.222222c3.328-2.929778 22.357333-2.929778 28.444445 0l56.888889 56.888889c2.929778 6.115556 2.929778 25.116444 0 28.444445L597.333333 512z" fill="#4a4a4a"></path></svg>
|
||||
</button>
|
||||
<div className="user-avatar-edit-content">
|
||||
<span className={'user-avatar-edit-tip' + (showBubble && !showUpload ? ' show' : '')}>点击图标修改头像</span>
|
||||
<span className={'user-avatar-edit-tip' + (showBubble && !showUpload ? ' show' : '')}>点击图标修改头像和昵称</span>
|
||||
<div className="avatar-upload">
|
||||
<div className={"upload-unit-progress" + (uploaded ? ' show' : '')}>
|
||||
<div className="icon">
|
||||
@@ -129,6 +150,19 @@ export default function AvatarUnit({ avatar, onChangeAvatar, showTip }) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="nickname-edit">
|
||||
<label>昵称:</label>
|
||||
<input type="text" value={nicknameNow} onChange={e => setNicknameNow(e.target.value)} />
|
||||
<button className="btn btn-primary" onClick={handleChangeNickname} disabled={nicknameUploading}>
|
||||
{
|
||||
nicknameUploading ? (
|
||||
<Spinner />
|
||||
) : (
|
||||
'保存'
|
||||
)
|
||||
}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@@ -25,6 +25,7 @@ export default function UserControl({ pageAuthLevel, buttonOnly }) {
|
||||
name: data.realName,
|
||||
role: data.role,
|
||||
avatar: data.avatar,
|
||||
nickname: data.nickname
|
||||
});
|
||||
});
|
||||
return;
|
||||
@@ -46,44 +47,49 @@ export default function UserControl({ pageAuthLevel, buttonOnly }) {
|
||||
<UserContext.Consumer>
|
||||
{({ userData, setUserData }) => (
|
||||
<div className="user">
|
||||
{buttonOnly || (
|
||||
<Fragment>
|
||||
{
|
||||
userData.role === -1
|
||||
? (
|
||||
<Spinner isGray />
|
||||
)
|
||||
: userData.role > 0 && (
|
||||
<AvatarUnit
|
||||
avatar={userData.avatar || images.avatar}
|
||||
onChangeAvatar={url => setUserData({ ...userData, avatar: url })}
|
||||
showTip={!userData.avatar}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
userData.role !== 0 && (
|
||||
<div className="user-name">{userData.name}</div>
|
||||
)
|
||||
}
|
||||
</Fragment>
|
||||
)}
|
||||
<button
|
||||
className="btn btn-hollow btn-straight"
|
||||
onClick={() => {
|
||||
localStorage.setItem('jwt', '');
|
||||
setUserData({ role: 0, name: '' });
|
||||
History.force('/login');
|
||||
}}
|
||||
>{userData.role > 0 ? '退出账号' : '登录'}</button>
|
||||
{
|
||||
userData.role === 2 && pageAuthLevel !== 2 && (
|
||||
<button
|
||||
className="btn btn-light btn-straight"
|
||||
onClick={() => {
|
||||
History.push('/admin/review');
|
||||
}}
|
||||
>进入审核</button>
|
||||
userData.role === -1
|
||||
? (
|
||||
<Spinner />
|
||||
) : (
|
||||
<Fragment>
|
||||
{buttonOnly || (
|
||||
<Fragment>
|
||||
{
|
||||
userData.role > 0 && (
|
||||
<AvatarUnit
|
||||
avatar={userData.avatar || images.avatar}
|
||||
onChangeAvatar={url => setUserData({ ...userData, avatar: url })}
|
||||
showTip={!userData.avatar}
|
||||
/>
|
||||
)
|
||||
}
|
||||
{
|
||||
userData.role !== 0 && (
|
||||
<div className="user-name">{userData.name}</div>
|
||||
)
|
||||
}
|
||||
</Fragment>
|
||||
)}
|
||||
<button
|
||||
className="btn btn-hollow btn-straight"
|
||||
onClick={() => {
|
||||
localStorage.setItem('jwt', '');
|
||||
setUserData({ role: 0, name: '' });
|
||||
History.force('/login');
|
||||
}}
|
||||
>{userData.role > 0 ? '退出账号' : '登录'}</button>
|
||||
{
|
||||
userData.role === 2 && pageAuthLevel !== 2 && (
|
||||
<button
|
||||
className="btn btn-light btn-straight"
|
||||
onClick={() => {
|
||||
History.push('/admin/review');
|
||||
}}
|
||||
>进入审核</button>
|
||||
)
|
||||
}
|
||||
</Fragment>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
|
@@ -5,6 +5,7 @@ export const apis = {
|
||||
login: backEndBaseURL + "/user/login",
|
||||
getProfile: backEndBaseURL + "/user/me",
|
||||
uploadAvatar: backEndBaseURL + "/user/uploadAvatar",
|
||||
updateNickname: backEndBaseURL + "/user/updateNickname",
|
||||
|
||||
submitMessage: backEndBaseURL + "/post/submit",
|
||||
listEssence: backEndBaseURL + "/post/listPublished?page=1",
|
||||
|
@@ -88,25 +88,34 @@ async function send(xhr, retryConf) {
|
||||
waitToSend.push(retryConf);
|
||||
console.log(err);
|
||||
// 等待列表不为空时弹框要么出现了要么就是在消失的路上,没有办法给予用户点击重试的机会,所以交由外部逻辑处理
|
||||
|
||||
const failPostProceess = () => {
|
||||
waitToSend.splice(waitToSend.indexOf(retryConf), 1);
|
||||
return failData;
|
||||
}
|
||||
|
||||
// 注意,理论上带有时间戳的请求是不可以重试的,但是这里不做那方面考虑,如果未来有需要,可以自己实现一个刷新时间戳重试的逻辑
|
||||
if (err.message === 'Network Error')
|
||||
return await failed('您的设备似乎断网了,或者服务器发生了问题,请检查网络后重试或刷新', flushWaitList) || failData;
|
||||
return (await failed('您的设备似乎断网了,或者服务器发生了问题,请检查网络后重试或刷新', flushWaitList(retryConf))) || failPostProceess();
|
||||
if (!err?.response?.status)
|
||||
return await failed('请求发生问题:' + err.message, flushWaitList) || failData;
|
||||
return (await failed('请求发生问题:' + err.message, flushWaitList(retryConf))) || failPostProceess();
|
||||
if (err.response.status === 504)
|
||||
return await failed('请求超时,请耐心等待几秒后重试或刷新', flushWaitList) || failData;
|
||||
return await failed('服务器出现问题,请稍后重试或刷新,错误代码' + err.response.status, flushWaitList) || failData;
|
||||
return (await failed('请求超时,请耐心等待几秒后重试或刷新', flushWaitList(retryConf))) || failPostProceess();
|
||||
return (await failed('服务器出现问题,请稍后重试或刷新,错误代码' + err.response.status, flushWaitList(retryConf))) || failPostProceess();
|
||||
}
|
||||
}
|
||||
|
||||
function flushWaitList() {
|
||||
let fns = waitToSend.map(
|
||||
conf =>
|
||||
async () => conf.resolver(await conf.fetcher())
|
||||
);
|
||||
waitToSend.splice(0, waitToSend.length);
|
||||
fns.forEach(fn => fn());
|
||||
function flushWaitList(confToRun) {
|
||||
return async () => {
|
||||
waitToSend.splice(waitToSend.indexOf(confToRun), 1);
|
||||
let fns = waitToSend.map(
|
||||
conf =>
|
||||
async () => conf.resolver ? conf.resolver(await conf.fetcher()) : await conf.fetcher()
|
||||
);
|
||||
waitToSend.splice(0, waitToSend.length);
|
||||
fns.forEach(fn => fn());
|
||||
return confToRun.resolver ? confToRun.resolver(await confToRun.fetcher()) : await confToRun.fetcher()
|
||||
};
|
||||
}
|
||||
|
||||
function feedWaitList(data) {
|
||||
|
@@ -62,7 +62,7 @@ export class AppContainer extends Component {
|
||||
<div className="post-header">
|
||||
<img class="post-avatar" src={post.avatar} alt="头像" />
|
||||
<div>
|
||||
<div class="post-username">{post.realName}</div>
|
||||
<div class="post-username">{post.nickname}</div>
|
||||
<div class="post-time">于{new Date(post.time * 1000).toLocaleString()}发布</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -44,12 +44,12 @@ export class UploadContainer extends Component {
|
||||
if (this.state.submitting && (!this.state.file || this.state.url !== "")) {
|
||||
post(apis.submitMessage, { content: this.state.msg, image: this.state.url })
|
||||
.then(({ data, status, networkStatus }) => {
|
||||
this.setState({ submitting: false });
|
||||
if (networkStatus !== 200) return;
|
||||
if (!status) {
|
||||
this.setState({ submitting: false });
|
||||
return alert('提交内容失败:' + data);
|
||||
}
|
||||
this.setState({ submitting: false, msg: "", url: "", file: null });
|
||||
this.setState({ msg: "", url: "", file: null });
|
||||
alert('内容提交成功啦');
|
||||
});
|
||||
}
|
||||
|
新增問題並參考
封鎖使用者