完成审核页面,对样式做大量微调(视觉的按钮好怪啊),但是目前发现了请求处理等待流程的bug

This commit is contained in:
wzhqwq 2021-08-01 00:11:33 +08:00
父節點 a1386d8870
當前提交 b8f99f4e4c
共有 16 個檔案被更改,包括 249 行新增118 行删除

查看文件

@ -2,50 +2,53 @@ a.btn {
text-decoration: none; text-decoration: none;
} }
.btn { .btn {
color: white !important; --btn-fg: #FFF;
border-radius: .3em; color: var(--btn-fg) !important;
padding: .375em 0; border-radius: .4em;
padding: .3em 0;
line-height: 1.5em; line-height: 1.5em;
font-size: 1.1em;
cursor: pointer; cursor: pointer;
transition: filter .3s ease-out; transition: filter .3s ease-out;
width: 5em; width: 5em;
text-align: center; text-align: center;
font-size: 18px; font-size: 18px;
border: 1px solid transparent;
} }
.btn:disabled { .btn:disabled {
cursor: not-allowed; cursor: not-allowed;
filter: grayscale(100%) opacity(.5); filter: grayscale(100%) opacity(.5);
} }
.btn:active { .btn:active {
color: white !important; color: var(--btn-fg) !important;
} }
.btn:hover(:not(:disabled)) { .btn:hover(:not(:disabled)) {
filter: brightness(1.2); filter: brightness(1.2);
} }
button.btn {
border: none;
}
.btn-primary { .btn-primary {
background-color: #318ffb; background-color: #0AF;
} }
.btn-ok { .btn-ok {
background-color: #47c74c; background-color: #47c74c;
} }
.btn-gray { .btn-gray {
background-color: #d9d9d9; background-color: #E8E8E8;
--btn-fg: #000;
} }
.btn-sdu { .btn-sdu {
background-color: #9D0004; background-color: #9D0004;
} }
.btn.btn-light { .btn-light {
background-color: #fcfafa; background-color: #fcfcfc;
color: #323232 !important; --btn-fg: #333;
} }
.btn.btn-light:active { .btn.btn-hollow {
color: #323232 !important; background-color: transparent;
border: 1px solid #FFF;
}
.btn.btn-dark.btn-hollow {
border: 1px solid #AAA;
--btn-fg: #333;
} }
.btn-circle { .btn-circle {
@ -56,10 +59,34 @@ button.btn {
box-shadow: 1px 1px 4px 0 #0003; box-shadow: 1px 1px 4px 0 #0003;
} }
.btn-straight { .btn-straight {
border-radius: .15em; border-radius: .2em;
width: 6em; width: 6em;
} }
.btn-pill {
border-radius: 1em;
padding: .15em 0;
width: 4em;
}
.col-center {
width: 100%;
display: flex;
justify-content: center;
}
.col-center > .spinner {
padding: 0;
margin: 0;
}
.banner {
height: 80px;
width: 100%;
display: flex;
justify-content: space-between;
padding: 10px 60px;
box-sizing: border-box;
background-color: #9D0004;
}
.sdu, .sdu,
.user { .user {
height: 100%; height: 100%;
@ -75,6 +102,10 @@ button.btn {
margin-left: 10px; margin-left: 10px;
} }
b {
color: #9E0004;
}
.content { .content {
width: 1000px; width: 1000px;
margin: 0 auto; margin: 0 auto;
@ -87,3 +118,11 @@ button.btn {
margin: 0 40px; margin: 0 40px;
} }
} }
/* modifying sweet alert */
.expanded-msg {
padding: 50px 0;
}
.swal2-actions > .btn {
margin: 0 40px;
}

查看文件

@ -3,7 +3,7 @@ import { Redirect, Route, SingleRouter } from './components/SingleRouter/SingleR
import { AppContainer } from './index/index'; import { AppContainer } from './index/index';
import { UploadContainer } from './upload/upload'; import { UploadContainer } from './upload/upload';
import { LogInContainer } from './login/login'; import { LogInContainer } from './login/login';
import { ReviewContainer } from './review/review'; import { ReviewContainer } from './admin/review/review';
import { Component } from 'react'; import { Component } from 'react';
import { UserContext } from './helper/Context'; import { UserContext } from './helper/Context';
@ -28,7 +28,7 @@ class App extends Component {
<Route path="/" component={AppContainer} /> <Route path="/" component={AppContainer} />
<Route path="/upload" component={UploadContainer} /> <Route path="/upload" component={UploadContainer} />
<Route path="/login" component={LogInContainer} /> <Route path="/login" component={LogInContainer} />
<Route path="/review" component={ReviewContainer} /> <Route path="/admin/review" component={ReviewContainer} />
<Route component={CheckLogIn} /> <Route component={CheckLogIn} />
</SingleRouter> </SingleRouter>
</UserContext.Provider> </UserContext.Provider>

查看文件

@ -0,0 +1,40 @@
.review-content {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.review-content > p {
width: calc(100% - 300px);
}
.review-img {
margin: 40px 0;
display: block;
cursor: zoom-in;
}
.review-img > img {
/* max-height: 300px; */
max-width: 800px;
}
.review-btns {
display: flex;
width: 100%;
justify-content: space-between;
border-top: 1px solid #DDD;
box-sizing: border-box;
padding: 40px 150px;
}
@media (max-width: 840px) {
.review-img > img {
max-width: 500px;
}
.review-content > p {
width: calc(100% - 100px);
}
.review-btns {
padding: 30px 50px;
}
}

108
src/admin/review/review.js Normal file
查看文件

@ -0,0 +1,108 @@
import './review.css';
import { Component } from 'react';
import UserControl from '../../components/UserControl/UserControl';
import { images } from '../../resources.json';
import { get, post } from '../../helper/axios';
import { apis } from '../../helper/apis';
import { alert, confirmWithClose } from '../../helper/alert';
import { UserContext } from '../../helper/Context';
import Spinner from '../../components/Spinner/Spinner';
export class ReviewContainer extends Component {
static contextType = UserContext;
fetched = false;
constructor(props) {
super(props);
this.state = {
posts: [],
fetchingPosts: false,
reviewingNow: 0,
actionPerforming: 0
};
}
componentDidMount() {
if (this.context.userData?.role === 2) this.fetchPosts();
}
componentDidUpdate() {
if (this.context.userData?.role === 2 && !this.fetched) this.fetchPosts();
}
fetchPosts() {
this.fetched = true;
this.setState({ fetchingPosts: true });
get(apis.listNotReviewed).then(({ data, status, networkStatus }) => {
this.setState({ fetchingPosts: false });
if (networkStatus !== 200) return;
if (!status) return alert('拉取审核列表失败:' + data + ',请刷新重试');
this.setState({ posts: data });
});
}
async review(accepted) {
// 哇提示语怎么这么生硬啊
if (!await confirmWithClose(`<div class="expanded-msg">确认<b>${accepted ? '发布' : '不通过'}</b>此条信息?</div>`)) return;
const { posts, reviewingNow } = this.state;
const { id } = posts[reviewingNow];
this.setState({ actionPerforming: accepted ? 1 : 2 });
post(apis.setStatus, { id, status: accepted ? 2 : 3 }).then(({ data, status, networkStatus }) => {
this.setState({ actionPerforming: 0 });
if (networkStatus !== 200) return;
if (!status) return alert('提交审核失败:' + data + ',请稍后重试');
if (this.state.reviewingNow === this.state.posts.length - 1)
this.fetchPosts();
else
this.setState({ reviewingNow: this.state.reviewingNow + 1 });
});
}
render() {
return (
<div className="review-container">
<div className="banner">
<div className="sdu">
<img src={images.icon} className="sdu-logo" alt="logo" />
<img src={images.name} className="sdu-name" alt="" />
</div>
<UserControl />
</div>
<div className="content">
{
this.state.fetchingPosts
? (
<div className="col-center">
<Spinner isGray />
<div style={{ marginLeft: '10px' }}>正在加载审核列表...</div>
</div>
)
: (
this.state.posts.length === 0
? (
<div className="col-center">暂时没有待审核的内容哦</div>
)
: (
<div className="review-content">
<p>{this.state.posts[this.state.reviewingNow].content}</p>
<a className="review-img" href={this.state.posts[this.state.reviewingNow].image} target="_blank" rel="noreferrer">
<img src={this.state.posts[this.state.reviewingNow].image} alt="待审核的配图" />
</a>
<div className="review-btns">
<button className="btn btn-primary" onClick={() => this.review(true)} disabled={this.state.actionPerforming !== 0}>
{this.state.actionPerforming === 1 ? (<Spinner />) : '发布'}
</button>
<button className="btn btn-gray" onClick={() => this.review(false)} disabled={this.state.actionPerforming !== 0}>
{this.state.actionPerforming === 2 ? (<Spinner />) : '不通过'}
</button>
</div>
</div>
)
)
}
</div>
</div>
);
}
}

查看文件

@ -1,17 +1,19 @@
import './spinner.css'; import './spinner.css';
export default function Spinner(props) { export default function Spinner({ isGray }) {
let base = isGray ? '888' : 'FFF';
return ( return (
<div className="spinner"> <div className="spinner">
<svg viewBox="0 0 1027 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"> <svg viewBox="0 0 1027 1024" version="1.1" xmlns="http://www.w3.org/2000/svg">
<path d="M101.725953 198.235466m48.271823-48.271823l0 0q48.271823-48.271823 96.543646 0l144.815469 144.815468q48.271823 48.271823 0 96.543646l0 0q-48.271823 48.271823-96.543646 0l-144.815469-144.815468q-48.271823-48.271823 0-96.543646Z" fill="#FFFD"></path> <path d="M101.725953 198.235466m48.271823-48.271823l0 0q48.271823-48.271823 96.543646 0l144.815469 144.815468q48.271823 48.271823 0 96.543646l0 0q-48.271823 48.271823-96.543646 0l-144.815469-144.815468q-48.271823-48.271823 0-96.543646Z" fill={`#${base}D`}></path>
<path d="M341.367467 443.733333m0 68.266667l0 0q0 68.266667-68.266667 68.266667l-204.8 0q-68.266667 0-68.266667-68.266667l0 0q0-68.266667 68.266667-68.266667l204.8 0q68.266667 0 68.266667 68.266667Z" fill="#FFFC"></path> <path d="M341.367467 443.733333m0 68.266667l0 0q0 68.266667-68.266667 68.266667l-204.8 0q-68.266667 0-68.266667-68.266667l0 0q0-68.266667 68.266667-68.266667l204.8 0q68.266667 0 68.266667 68.266667Z" fill={`#${base}C`}></path>
<path d="M343.085068 584.40542m48.271823 48.271823l0 0q48.271823 48.271823 0 96.543646l-144.815469 144.815468q-48.271823 48.271823-96.543646 0l0 0q-48.271823-48.271823 0-96.543646l144.815469-144.815468q48.271823-48.271823 96.543646 0Z" fill="#FFFB"></path> <path d="M343.085068 584.40542m48.271823 48.271823l0 0q48.271823 48.271823 0 96.543646l-144.815469 144.815468q-48.271823 48.271823-96.543646 0l0 0q-48.271823-48.271823 0-96.543646l144.815469-144.815468q48.271823-48.271823 96.543646 0Z" fill={`#${base}B`}></path>
<path d="M443.767467 682.666667m68.266666 0l0 0q68.266667 0 68.266667 68.266666l0 204.8q0 68.266667-68.266667 68.266667l0 0q-68.266667 0-68.266666-68.266667l0-204.8q0-68.266667 68.266666-68.266666Z" fill="#FFFA"></path> <path d="M443.767467 682.666667m68.266666 0l0 0q68.266667 0 68.266667 68.266666l0 204.8q0 68.266667-68.266667 68.266667l0 0q-68.266667 0-68.266666-68.266667l0-204.8q0-68.266667 68.266666-68.266666Z" fill={`#${base}A`}></path>
<path d="M584.439553 680.949066m48.271823-48.271823l0 0q48.271823-48.271823 96.543646 0l144.815469 144.815468q48.271823 48.271823 0 96.543646l0 0q-48.271823 48.271823-96.543646 0l-144.815469-144.815468q-48.271823-48.271823 0-96.543646Z" fill="#FFF9"></path> <path d="M584.439553 680.949066m48.271823-48.271823l0 0q48.271823-48.271823 96.543646 0l144.815469 144.815468q48.271823 48.271823 0 96.543646l0 0q-48.271823 48.271823-96.543646 0l-144.815469-144.815468q-48.271823-48.271823 0-96.543646Z" fill={`#${base}9`}></path>
<path d="M1024.034133 443.733333m0 68.266667l0 0q0 68.266667-68.266666 68.266667l-204.8 0q-68.266667 0-68.266667-68.266667l0 0q0-68.266667 68.266667-68.266667l204.8 0q68.266667 0 68.266666 68.266667Z" fill="#FFF8"></path> <path d="M1024.034133 443.733333m0 68.266667l0 0q0 68.266667-68.266666 68.266667l-204.8 0q-68.266667 0-68.266667-68.266667l0 0q0-68.266667 68.266667-68.266667l204.8 0q68.266667 0 68.266666 68.266667Z" fill={`#${base}8`}></path>
<path d="M825.798668 101.69182m48.271823 48.271823l0 0q48.271823 48.271823 0 96.543646l-144.815469 144.815468q-48.271823 48.271823-96.543646 0l0 0q-48.271823-48.271823 0-96.543646l144.815469-144.815468q48.271823-48.271823 96.543646 0Z" fill="#FFF7"></path> <path d="M825.798668 101.69182m48.271823 48.271823l0 0q48.271823 48.271823 0 96.543646l-144.815469 144.815468q-48.271823 48.271823-96.543646 0l0 0q-48.271823-48.271823 0-96.543646l144.815469-144.815468q48.271823-48.271823 96.543646 0Z" fill={`#${base}7`}></path>
<path d="M443.767467 0m68.266666 0l0 0q68.266667 0 68.266667 68.266667l0 204.8q0 68.266667-68.266667 68.266666l0 0q-68.266667 0-68.266666-68.266666l0-204.8q0-68.266667 68.266666-68.266667Z" fill="#FFF6"></path></svg> <path d="M443.767467 0m68.266666 0l0 0q68.266667 0 68.266667 68.266667l0 204.8q0 68.266667-68.266667 68.266666l0 0q-68.266667 0-68.266666-68.266666l0-204.8q0-68.266667 68.266666-68.266667Z" fill={`#${base}6`}></path>
</svg>
</div> </div>
); );
} }

查看文件

@ -91,6 +91,7 @@
justify-content: center; justify-content: center;
display: none; display: none;
z-index: 1; z-index: 1;
cursor: zoom-out;
} }
.scaled-wrap.open { .scaled-wrap.open {
display: flex; display: flex;

查看文件

@ -16,7 +16,11 @@ export default function UserControl(props) {
<div className="user"> <div className="user">
<button <button
className="btn btn-hollow btn-straight" className="btn btn-hollow btn-straight"
onClick={() => setUserData({ role: -1, name: '' })} onClick={() => {
localStorage.setItem('jwt', '');
setUserData({ role: -1, name: '' });
History.force('/login');
}}
>退出审核</button> >退出审核</button>
</div> </div>
) : ( ) : (
@ -34,9 +38,9 @@ export default function UserControl(props) {
<div className="user-name">{userData.name || "加载中"}</div> <div className="user-name">{userData.name || "加载中"}</div>
{ {
(() => { (() => {
if (userData.role !== 2 && History.getHref().match(/^\/admin.*/)) if (userData.role === 1 && History.getHref().match(/^\/admin.*/))
History.force('/'); History.force('/');
if (userData.role !== -1) return null; if (userData.role !== -1 || !localStorage.getItem('jwt')) return null;
get(apis.getProfile).then(({ data, status, networkStatus }) => { get(apis.getProfile).then(({ data, status, networkStatus }) => {
if (networkStatus !== 200) return; if (networkStatus !== 200) return;
if (!status) return alert('获取用户信息失败:' + data + ',请稍候刷新再试'); if (!status) return alert('获取用户信息失败:' + data + ',请稍候刷新再试');

查看文件

@ -2,8 +2,8 @@ import Swal from 'sweetalert2';
const SelfSwal = Swal.mixin({ const SelfSwal = Swal.mixin({
customClass: { customClass: {
confirmButton: 'btn btn-primary', confirmButton: 'btn btn-primary btn-pill',
cancelButton: 'btn btn-gray', cancelButton: 'btn btn-dark btn-hollow btn-pill',
}, },
buttonsStyling: false, buttonsStyling: false,
}); });
@ -23,7 +23,8 @@ export const failed = (content, afterRetry) => {
html: content, html: content,
showCancelButton: true, showCancelButton: true,
confirmButtonText: '重试', confirmButtonText: '重试',
cancelButtonText: '放弃' cancelButtonText: '放弃',
icon: 'error'
}) })
.then(result => result.isConfirmed ? afterRetry() : null) : .then(result => result.isConfirmed ? afterRetry() : null) :
SelfSwal.fire({ SelfSwal.fire({
@ -39,6 +40,16 @@ export const alert = (content) => {
return SelfSwal.fire({ return SelfSwal.fire({
html: content, html: content,
confirmButtonText: '了解', confirmButtonText: '了解',
icon: 'warning' icon: 'info'
}); });
} }
export const confirmWithClose = async (content) => {
return (await SelfSwal.fire({
html: content,
confirmButtonText: '确定',
showCancelButton: true,
cancelButtonText: '取消',
showCloseButton: true,
})).isConfirmed;
}

查看文件

@ -8,6 +8,6 @@ export const apis = {
submitMessage: backEndBaseURL + "/post/submit", submitMessage: backEndBaseURL + "/post/submit",
listEssence: backEndBaseURL + "/post/listPublished?page=1", listEssence: backEndBaseURL + "/post/listPublished?page=1",
listAll: backEndBaseURL + "/admin/list", listNotReviewed: backEndBaseURL + "/admin/list/1",
setStatus: backEndBaseURL + "/admin/setStatus" setStatus: backEndBaseURL + "/admin/setStatus"
}; };

查看文件

@ -11,7 +11,7 @@ export function get(url) {
"Allow-Control-Allow-Origin": "*" "Allow-Control-Allow-Origin": "*"
} }
}), }),
() => ({ fn: get(url), identifier: 'get:' + url }) { fn: () => get(url), identifier: 'get:' + url }
); );
} }
export function post(url, data) { export function post(url, data) {
@ -22,7 +22,7 @@ export function post(url, data) {
"Allow-Control-Allow-Origin": "*" "Allow-Control-Allow-Origin": "*"
} }
}), }),
() => ({ fn: post(url, data), identifier: 'post:' + url + ' ' + JSON.stringify(data) }) { fn: () => post(url, data), identifier: 'post:' + url + ' ' + JSON.stringify(data) }
); );
} }
@ -63,6 +63,7 @@ async function send(xhr, retryConf) {
} }
function flushWaitList() { function flushWaitList() {
waitToSend.forEach(retryFunc => retryFunc()); let fns = waitToSend.map(item => item.fn);
waitToSend.splice(0, waitToSend.length); waitToSend.splice(0, waitToSend.length);
fns.forEach(fn => fn());
} }

查看文件

@ -26,12 +26,6 @@
font-size: 18px; font-size: 18px;
border-bottom: 1px solid #e6e6e6; border-bottom: 1px solid #e6e6e6;
} }
.card-center {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.card-body { .card-body {
padding: 10px; padding: 10px;
} }

查看文件

@ -48,9 +48,9 @@ export class AppContainer extends Component {
<div className="card-body"> <div className="card-body">
{ {
this.state.fetchingEssential this.state.fetchingEssential
? <div className="card-center"><Spinner /></div> ? <div className="col-center"><Spinner isGray /></div>
: (this.state.essentialMessages.length === 0 : (this.state.essentialMessages.length === 0
? (<div className="card-center">暂时没有精选留言</div>) ? (<div className="col-center">暂时没有精选留言</div>)
: ( : (
<ul className="message-list"> <ul className="message-list">
{this.state.essentialMessages.map(msg => ( {this.state.essentialMessages.map(msg => (
@ -69,9 +69,9 @@ export class AppContainer extends Component {
<div className="card-body"> <div className="card-body">
{ {
this.state.fetchingEssential this.state.fetchingEssential
? <div className="card-center"><Spinner /></div> ? <div className="col-center"><Spinner isGray /></div>
: (this.state.essentialImages.length === 0 : (this.state.essentialImages.length === 0
? (<div className="card-center">暂时没有精选图片</div>) ? (<div className="col-center">暂时没有精选图片</div>)
: this.state.essentialImages.map(src => ( : this.state.essentialImages.map(src => (
<img src={src} alt="精选图片" /> <img src={src} alt="精选图片" />
)) ))

查看文件

@ -1,58 +0,0 @@
import { Component } from 'react';
import './review.css';
export class ReviewContainer extends Component {
constructor(props) {
super(props);
this.state = {
rating: 0,
comment: '',
showComment: false,
};
}
handleClick(e) {
this.setState({
showComment: !this.state.showComment,
});
}
handleChange(e) {
this.setState({
rating: e.target.value,
});
}
handleCommentChange(e) {
this.setState({
comment: e.target.value,
});
}
render() {
return (
<div className="review">
<div className="rating">
<div className="star-rating">
<div className="star" />
<div className="star" />
<div className="star" />
<div className="star" />
</div>
</div>
<div className="comment">
<textarea
className="comment-input"
value={this.state.comment}
onChange={this.handleCommentChange.bind(this)}
/>
<div className="comment-button">
<button onClick={this.handleClick.bind(this)}>
Comment
</button>
</div>
</div>
</div>
);
}
}

查看文件

@ -1,13 +1,3 @@
.banner {
height: 80px;
width: 100%;
display: flex;
justify-content: space-between;
padding: 10px 80px;
box-sizing: border-box;
background-color: #9D0004;
}
.message-box { .message-box {
width: 100%; width: 100%;
height: 300px; height: 300px;

查看文件

@ -40,7 +40,6 @@ export class UploadContainer extends Component {
componentDidUpdate() { componentDidUpdate() {
if (this.state.submitting && (!this.state.file || this.state.url !== "")) { if (this.state.submitting && (!this.state.file || this.state.url !== "")) {
// upload using axios
post(apis.submitMessage, { content: this.state.msg, image: this.state.url }) post(apis.submitMessage, { content: this.state.msg, image: this.state.url })
.then(({ data, status, networkStatus }) => { .then(({ data, status, networkStatus }) => {
if (networkStatus !== 200) return; if (networkStatus !== 200) return;