比較提交
沒有共同的提交。「b0f33d0bf40f7f0e8d5315ae8b688ffc61708e69」和「e2fb2ccc2b8f2dcfea70aad38a39354af3634b8b」的歷史完全不同。
b0f33d0bf4
...
e2fb2ccc2b
15
src/App.css
15
src/App.css
@ -101,21 +101,6 @@ a.btn {
|
|||||||
height: 30px;
|
height: 30px;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
.nav {
|
|
||||||
display: flex;
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
.nav-item {
|
|
||||||
padding: 12px 16px;
|
|
||||||
}
|
|
||||||
.nav-link {
|
|
||||||
text-decoration: none;
|
|
||||||
color: white;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
.nav-link:active {
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
b {
|
b {
|
||||||
color: #9E0004;
|
color: #9E0004;
|
||||||
|
@ -7,7 +7,6 @@ import { apis } from '../../helper/apis';
|
|||||||
import { alert, confirmWithClose } from '../../helper/alert';
|
import { alert, confirmWithClose } from '../../helper/alert';
|
||||||
import { UserContext } from '../../helper/Context';
|
import { UserContext } from '../../helper/Context';
|
||||||
import Spinner from '../../components/Spinner/Spinner';
|
import Spinner from '../../components/Spinner/Spinner';
|
||||||
import { Link } from '../../components/SingleRouter/SingleRouter';
|
|
||||||
|
|
||||||
export class ReviewContainer extends Component {
|
export class ReviewContainer extends Component {
|
||||||
static contextType = UserContext;
|
static contextType = UserContext;
|
||||||
@ -67,11 +66,6 @@ export class ReviewContainer extends Component {
|
|||||||
<div className="sdu">
|
<div className="sdu">
|
||||||
<img src={images.icon} className="sdu-logo" alt="logo" />
|
<img src={images.icon} className="sdu-logo" alt="logo" />
|
||||||
<img src={images.name} className="sdu-name" alt="" />
|
<img src={images.name} className="sdu-name" alt="" />
|
||||||
<div className="nav">
|
|
||||||
<div className="nav-item">
|
|
||||||
<Link to="/" className="nav-link">首页</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<UserControl pageAuthLevel={2} />
|
<UserControl pageAuthLevel={2} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,8 +0,0 @@
|
|||||||
.preview {
|
|
||||||
width: 200px;
|
|
||||||
height: 200px;
|
|
||||||
}
|
|
||||||
.preview > img {
|
|
||||||
height: 200px;
|
|
||||||
cursor: zoom-in;
|
|
||||||
}
|
|
@ -1,69 +0,0 @@
|
|||||||
import './ImagePreview.css';
|
|
||||||
|
|
||||||
import { useCallback, useRef, useState, useEffect, Fragment } from 'react';
|
|
||||||
import Spinner from '../Spinner/Spinner';
|
|
||||||
|
|
||||||
export default function ImagePreview({ image }) {
|
|
||||||
const [isScaled, setIsScaled] = useState(false);
|
|
||||||
const [transform, setTransform] = useState('');
|
|
||||||
const imgRef = useRef(null);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const imgWidth = useRef(0), imgHeight = useRef(0);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setLoading(true);
|
|
||||||
let img = new Image();
|
|
||||||
img.onload = () => {
|
|
||||||
setLoading(false);
|
|
||||||
imgWidth.current = img.width;
|
|
||||||
imgHeight.current = img.height;
|
|
||||||
};
|
|
||||||
img.src = image;
|
|
||||||
}, [image]);
|
|
||||||
|
|
||||||
const scale = useCallback(() => {
|
|
||||||
if (isScaled) return;
|
|
||||||
let el = imgRef.current;
|
|
||||||
let { x, y } = el.getBoundingClientRect();
|
|
||||||
let { innerWidth, innerHeight } = window;
|
|
||||||
let initialScale = 200 / imgHeight.current,
|
|
||||||
terminalScale = Math.min(1, Math.min((innerWidth - 10) / imgWidth.current, (innerHeight - 10) / imgHeight.current));
|
|
||||||
setTransform(`translate(${x + (imgWidth.current * initialScale - innerWidth) / 2}px, ${y + (200 - innerHeight) / 2}px) scale(${initialScale})`);
|
|
||||||
setIsScaled(true);
|
|
||||||
setTimeout(() => {
|
|
||||||
setTransform(`translate(0, 0) scale(${terminalScale})`);
|
|
||||||
}, 0);
|
|
||||||
}, [isScaled]);
|
|
||||||
|
|
||||||
const unscale = useCallback(() => {
|
|
||||||
if (!isScaled) return;
|
|
||||||
let el = imgRef.current;
|
|
||||||
let { x, y } = el.getBoundingClientRect();
|
|
||||||
let { innerWidth, innerHeight } = window;
|
|
||||||
let initialScale = 200 / imgHeight.current;
|
|
||||||
setTransform(`translate(${x + (imgWidth.current * initialScale - innerWidth) / 2}px, ${y + (200 - innerHeight) / 2}px) scale(${initialScale})`);
|
|
||||||
setIsScaled(true);
|
|
||||||
setTimeout(() => {
|
|
||||||
setIsScaled(false);
|
|
||||||
}, 300);
|
|
||||||
}, [isScaled]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="preview">
|
|
||||||
{
|
|
||||||
loading ? (
|
|
||||||
<div className="spinner-wrap">
|
|
||||||
<Spinner isGray />
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<Fragment>
|
|
||||||
<img src={image} alt="图片" onClick={scale} ref={imgRef} />
|
|
||||||
<div className={isScaled ? 'scaled-wrap open' : 'scaled-wrap'} onClick={unscale}>
|
|
||||||
<img className="scaled-image" src={image} alt="图片" style={{ transform }} />
|
|
||||||
</div>
|
|
||||||
</Fragment>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
@ -9,14 +9,6 @@
|
|||||||
animation: spin 1s steps(8, start) infinite;
|
animation: spin 1s steps(8, start) infinite;
|
||||||
}
|
}
|
||||||
|
|
||||||
.spinner-wrap {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
@keyframes spin {
|
||||||
from {
|
from {
|
||||||
transform: rotate(0deg);
|
transform: rotate(0deg);
|
||||||
|
@ -10,6 +10,7 @@ import { alert } from '../../helper/alert';
|
|||||||
import History from '../../helper/history';
|
import History from '../../helper/history';
|
||||||
import AvatarUnit from '../AvatarUnit/AvatarUnit';
|
import AvatarUnit from '../AvatarUnit/AvatarUnit';
|
||||||
|
|
||||||
|
const exitContent = ['登录', '退出', '退出审核'];
|
||||||
// page level: 0: everyone, 1: login needed, 2: admin only
|
// page level: 0: everyone, 1: login needed, 2: admin only
|
||||||
export default function UserControl({ pageAuthLevel, buttonOnly }) {
|
export default function UserControl({ pageAuthLevel, buttonOnly }) {
|
||||||
const { userData, setUserData } = useContext(UserContext);
|
const { userData, setUserData } = useContext(UserContext);
|
||||||
@ -68,18 +69,21 @@ export default function UserControl({ pageAuthLevel, buttonOnly }) {
|
|||||||
}
|
}
|
||||||
</Fragment>
|
</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 && (
|
userData.role === pageAuthLevel
|
||||||
|
? (
|
||||||
<button
|
<button
|
||||||
className="btn btn-light btn-straight"
|
className="btn btn-hollow btn-straight"
|
||||||
|
onClick={() => {
|
||||||
|
localStorage.setItem('jwt', '');
|
||||||
|
setUserData({ role: 0, name: '' });
|
||||||
|
History.force('/login');
|
||||||
|
}}
|
||||||
|
>{exitContent[pageAuthLevel]}</button>
|
||||||
|
)
|
||||||
|
: userData.role === 2 && (
|
||||||
|
<button
|
||||||
|
className="btn btn-hollow btn-straight"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
History.push('/admin/review');
|
History.push('/admin/review');
|
||||||
}}
|
}}
|
||||||
|
@ -6,6 +6,3 @@
|
|||||||
color: white;
|
color: white;
|
||||||
margin: 0 10px;
|
margin: 0 10px;
|
||||||
}
|
}
|
||||||
.user > :not(:first-child) {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
@ -84,9 +84,8 @@ async function send(xhr, retryConf) {
|
|||||||
return failData;
|
return failData;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (waitToSend.length) return failData;
|
if (!waitToSend.length) return failData;
|
||||||
waitToSend.push(retryConf);
|
waitToSend.push(retryConf);
|
||||||
console.log(err);
|
|
||||||
// 等待列表不为空时弹框要么出现了要么就是在消失的路上,没有办法给予用户点击重试的机会,所以交由外部逻辑处理
|
// 等待列表不为空时弹框要么出现了要么就是在消失的路上,没有办法给予用户点击重试的机会,所以交由外部逻辑处理
|
||||||
|
|
||||||
// 注意,理论上带有时间戳的请求是不可以重试的,但是这里不做那方面考虑,如果未来有需要,可以自己实现一个刷新时间戳重试的逻辑
|
// 注意,理论上带有时间戳的请求是不可以重试的,但是这里不做那方面考虑,如果未来有需要,可以自己实现一个刷新时间戳重试的逻辑
|
||||||
|
@ -1,20 +1,15 @@
|
|||||||
.index-container {
|
|
||||||
background-color: #F4F4F4;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.poster {
|
.poster {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 400px;
|
height: 400px;
|
||||||
background-color: #84000B;
|
background-color: #84000B;
|
||||||
background-position: center;
|
|
||||||
background-size: contain;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
.img-poster {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
.index-btns {
|
.index-btns {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: 40px;
|
right: 40px;
|
||||||
@ -25,6 +20,7 @@
|
|||||||
.index-btns > .user {
|
.index-btns > .user {
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
|
/* 这里的卡片和分栏结构就比较接近bootstrap了,可惜视觉做的太古板,如果愿意的话改成Material Design就更好了 */
|
||||||
.split-lg > .card {
|
.split-lg > .card {
|
||||||
width: 440px;
|
width: 440px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
@ -33,7 +29,7 @@
|
|||||||
.card-header {
|
.card-header {
|
||||||
padding: .5em;
|
padding: .5em;
|
||||||
font-size: 20px;
|
font-size: 20px;
|
||||||
font-weight: bold;
|
border-bottom: 1px solid #e6e6e6;
|
||||||
}
|
}
|
||||||
.card-body {
|
.card-body {
|
||||||
padding: 10px 5px;
|
padding: 10px 5px;
|
||||||
@ -45,47 +41,42 @@
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.img-list {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin: -3px -5px;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
.img-list > img {
|
||||||
|
width: 200px;
|
||||||
|
margin: 3px 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-list {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
.message-list > li {
|
||||||
|
line-height: 20px;
|
||||||
|
padding: 8px 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
/* list disk */
|
||||||
|
.message-list > li::before {
|
||||||
|
content: "";
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
background-color: #DADADA;
|
||||||
|
margin-right: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 980px) {
|
@media (max-width: 980px) {
|
||||||
.split-lg > .card {
|
.split-lg > .card {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.posts {
|
|
||||||
|
|
||||||
}
|
|
||||||
.post {
|
|
||||||
padding: 10px;
|
|
||||||
border-radius: 12px;
|
|
||||||
background-color: white;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
box-shadow: 2px 2px 4px #0001;
|
|
||||||
}
|
|
||||||
.post > p {
|
|
||||||
padding-left: 40px;
|
|
||||||
}
|
|
||||||
.post-header {
|
|
||||||
padding: 5px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
}
|
|
||||||
.post-body {
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
.post-avatar {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border-radius: 20px;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
.post-username {
|
|
||||||
}
|
|
||||||
.post-time {
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
.post-image {
|
|
||||||
width: 200px;
|
|
||||||
height: 200px;
|
|
||||||
}
|
|
@ -1,5 +1,4 @@
|
|||||||
import { Component } from 'react';
|
import { Component } from 'react';
|
||||||
import ImagePreview from '../components/ImagePreview/ImagePreview';
|
|
||||||
import { Link } from '../components/SingleRouter/SingleRouter';
|
import { Link } from '../components/SingleRouter/SingleRouter';
|
||||||
import Spinner from '../components/Spinner/Spinner';
|
import Spinner from '../components/Spinner/Spinner';
|
||||||
import UserControl from '../components/UserControl/UserControl';
|
import UserControl from '../components/UserControl/UserControl';
|
||||||
@ -13,7 +12,8 @@ export class AppContainer extends Component {
|
|||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
posts: [],
|
essentialMessages: [],
|
||||||
|
essentialImages: [],
|
||||||
fetchingEssential: false,
|
fetchingEssential: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -25,20 +25,26 @@ export class AppContainer extends Component {
|
|||||||
this.setState({ fetchingEssential: false });
|
this.setState({ fetchingEssential: false });
|
||||||
if (networkStatus !== 200) return;
|
if (networkStatus !== 200) return;
|
||||||
if (!status) return alert('获取精选列表失败:' + data + ',请刷新重试');
|
if (!status) return alert('获取精选列表失败:' + data + ',请刷新重试');
|
||||||
this.setState({ posts: data });
|
let messages = [], images = [];
|
||||||
|
data.forEach(post => {
|
||||||
|
if (post.content) messages.push(post.content);
|
||||||
|
if (post.image) images.push(post.image);
|
||||||
|
});
|
||||||
|
this.setState({ essentialMessages: messages, essentialImages: images });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="index-container">
|
<div className="index-container">
|
||||||
<div className="poster" style={{ backgroundImage: `url(${images.poster})` }}>
|
<div className="poster">
|
||||||
|
<img src={images.poster} className="img-poster" alt="海报" />
|
||||||
<div className='index-btns'>
|
<div className='index-btns'>
|
||||||
<UserControl pageAuthLevel={1} buttonOnly />
|
<UserControl pageAuthLevel={1} buttonOnly />
|
||||||
<Link to="/upload" className="btn btn-light btn-straight btn-partIn">点击参加</Link>
|
<Link to="/upload" className="btn btn-light btn-straight btn-partIn">点击参加</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="content">
|
<div className="content split-lg">
|
||||||
<div className="card">
|
<div className="card">
|
||||||
<div className="card-header">
|
<div className="card-header">
|
||||||
精选留言
|
精选留言
|
||||||
@ -50,33 +56,43 @@ export class AppContainer extends Component {
|
|||||||
<div className="col-center"><Spinner isGray /></div>
|
<div className="col-center"><Spinner isGray /></div>
|
||||||
)
|
)
|
||||||
: (
|
: (
|
||||||
this.state.posts.length === 0
|
this.state.essentialMessages.length === 0
|
||||||
? (
|
? (
|
||||||
<div className="col-center">暂时没有精选留言</div>
|
<div className="col-center">暂时没有精选留言</div>
|
||||||
)
|
)
|
||||||
: (
|
: (
|
||||||
<div className="posts">
|
<ul className="message-list">
|
||||||
{
|
{
|
||||||
this.state.posts.map((post, i) => (
|
this.state.essentialMessages.map((msg, i) => (
|
||||||
<div className="post" key={i}>
|
<li dangerouslySetInnerHTML={{ __html: msg }} key={i}></li>
|
||||||
<div className="post-header">
|
))
|
||||||
<img class="post-avatar" src={post.avatar} alt="头像" />
|
}
|
||||||
<div>
|
</ul>
|
||||||
<div class="post-username">{post.realName}</div>
|
)
|
||||||
<div class="post-time">于{new Date(post.time * 1000).toLocaleString()}发布</div>
|
)
|
||||||
</div>
|
}
|
||||||
</div>
|
</div>
|
||||||
<div className="post-body">
|
</div>
|
||||||
<p dangerouslySetInnerHTML={{ __html: post.content }}></p>
|
<div className="card">
|
||||||
{
|
<div className="card-header">
|
||||||
post.image && (
|
精选图片
|
||||||
<div className="post-image">
|
</div>
|
||||||
<ImagePreview image={post.image} />
|
<div className="card-body">
|
||||||
</div>
|
{
|
||||||
)
|
this.state.fetchingEssential
|
||||||
}
|
? (
|
||||||
</div>
|
<div className="col-center"><Spinner isGray /></div>
|
||||||
</div>
|
)
|
||||||
|
: (
|
||||||
|
this.state.essentialImages.length === 0
|
||||||
|
? (
|
||||||
|
<div className="col-center">暂时没有精选图片</div>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<div className="img-list">
|
||||||
|
{
|
||||||
|
this.state.essentialImages.map((src, i) => (
|
||||||
|
<img src={src} key={i} alt="精选图片" />
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
@ -21,7 +21,7 @@
|
|||||||
}
|
}
|
||||||
.form-group > input:focus {
|
.form-group > input:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: transparent;
|
border-color: #FDFDFD;
|
||||||
background-color: #FDFDFD;
|
background-color: #FDFDFD;
|
||||||
box-shadow: 1px 3px 6px #0001;
|
box-shadow: 1px 3px 6px #0001;
|
||||||
transform: translate(-2px, -2px);
|
transform: translate(-2px, -2px);
|
||||||
|
@ -8,7 +8,6 @@ import { images } from '../resources.json';
|
|||||||
|
|
||||||
import './upload.css';
|
import './upload.css';
|
||||||
import UserControl from '../components/UserControl/UserControl';
|
import UserControl from '../components/UserControl/UserControl';
|
||||||
import { Link } from '../components/SingleRouter/SingleRouter';
|
|
||||||
|
|
||||||
export class UploadContainer extends Component {
|
export class UploadContainer extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
@ -68,11 +67,6 @@ export class UploadContainer extends Component {
|
|||||||
<div className="sdu">
|
<div className="sdu">
|
||||||
<img src={images.icon} className="sdu-logo" alt="logo" />
|
<img src={images.icon} className="sdu-logo" alt="logo" />
|
||||||
<img src={images.name} className="sdu-name" alt="" />
|
<img src={images.name} className="sdu-name" alt="" />
|
||||||
<div className="nav">
|
|
||||||
<div className="nav-item">
|
|
||||||
<Link to="/" className="nav-link">首页</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<UserControl pageAuthLevel={1} />
|
<UserControl pageAuthLevel={1} />
|
||||||
</div>
|
</div>
|
||||||
|
載入中…
x
新增問題並參考
Block a user