forked from newthuhole/hole_thu_frontend
update
add search and refresh add lazyload fix ui
This commit is contained in:
5
package-lock.json
generated
5
package-lock.json
generated
@@ -8783,6 +8783,11 @@
|
||||
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-4.0.0.tgz",
|
||||
"integrity": "sha512-FlsPxavEyMuR6TjVbSSywovXSEyOg6ZDj5+Z8nbsRl9EkOzAhEIcS+GLoQDC5fz/t9suhUXWmUrOBrgeUvrMxw=="
|
||||
},
|
||||
"react-lazyload": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/react-lazyload/-/react-lazyload-2.3.0.tgz",
|
||||
"integrity": "sha512-0z3qmL+qtSERdfKFpn0yKXm+1Gg1ZLZBXnCzHhSGiu1L8iDARuCkbOypxEx9+ETxZvMnXj98xvWCs5jyXTuM2w=="
|
||||
},
|
||||
"react-scripts": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-1.1.4.tgz",
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"dependencies": {
|
||||
"react": "^16.4.2",
|
||||
"react-dom": "^16.4.2",
|
||||
"react-lazyload": "^2.3.0",
|
||||
"react-scripts": "1.1.4",
|
||||
"react-timeago": "^4.1.9"
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="viewport" content="width=device-width, shrink-to-fit=yes">
|
||||
<title>P大树洞(非官方)</title>
|
||||
</head>
|
||||
<body>
|
||||
@@ -20,10 +20,6 @@
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
<script>
|
||||
if(/Android|iPhone|iPad|iPod/.test(navigator.userAgent))
|
||||
alert('你为什么不用 PKU Helper 客户端呢?');
|
||||
</script>
|
||||
<script>
|
||||
var cnzz_s_tag = document.createElement('script');
|
||||
cnzz_s_tag.type = 'text/javascript';
|
||||
|
||||
16
src/App.js
16
src/App.js
@@ -2,6 +2,7 @@ import React, {Component} from 'react';
|
||||
import {Flow} from './Flows';
|
||||
import {Title} from './Title';
|
||||
import {Sidebar} from './Sidebar';
|
||||
import {ControlBar} from './ControlBar';
|
||||
|
||||
class App extends Component {
|
||||
constructor(props) {
|
||||
@@ -9,6 +10,8 @@ class App extends Component {
|
||||
this.state={
|
||||
sidebar_title: null,
|
||||
sidebar_content: null,
|
||||
search_text: null,
|
||||
flow_render_key: +new Date(),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -19,12 +22,23 @@ class App extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
set_search_text(text) {
|
||||
this.setState({
|
||||
search_text: text,
|
||||
flow_render_key: +new Date(),
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<div className="bg-img" />
|
||||
<Title callback={this.show_sidebar.bind(this)} />
|
||||
<div className="left-container">
|
||||
<Flow callback={this.show_sidebar.bind(this)} mode="list" />
|
||||
<ControlBar set_search_text={this.set_search_text.bind(this)} />
|
||||
<Flow key={this.state.flow_render_key}
|
||||
callback={this.show_sidebar.bind(this)} search_text={this.state.search_text}
|
||||
/>
|
||||
</div>
|
||||
<Sidebar do_close={()=>{
|
||||
this.setState({
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.centered-line::before,
|
||||
@@ -23,4 +24,15 @@
|
||||
.centered-line::after {
|
||||
left: 0.5em;
|
||||
margin-right: -50%;
|
||||
}
|
||||
|
||||
.bg-img {
|
||||
position: fixed;
|
||||
z-index: -1;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: midnightblue url(/eriri_bg.jpg) fixed center center;
|
||||
background-size: cover;
|
||||
}
|
||||
@@ -24,7 +24,7 @@ export function Time(props) {
|
||||
|
||||
export function CenteredLine(props) {
|
||||
return (
|
||||
<p className="centered-line">
|
||||
<p className="centered-line aux-margin">
|
||||
<span>{props.text}</span>
|
||||
</p>
|
||||
)
|
||||
|
||||
17
src/ControlBar.css
Normal file
17
src/ControlBar.css
Normal file
@@ -0,0 +1,17 @@
|
||||
.control-bar {
|
||||
display: flex;
|
||||
line-height: 2em;
|
||||
}
|
||||
|
||||
.control-bar .refresh-btn {
|
||||
flex: 0 0 100px;
|
||||
color: black;
|
||||
background-color: rgba(255,255,255,.9);
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
.control-bar input {
|
||||
flex: auto;
|
||||
color: black;
|
||||
background-color: rgba(255,255,255,.9);
|
||||
}
|
||||
42
src/ControlBar.js
Normal file
42
src/ControlBar.js
Normal file
@@ -0,0 +1,42 @@
|
||||
import React, {Component} from 'react';
|
||||
import './ControlBar.css';
|
||||
|
||||
export class ControlBar extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state={
|
||||
search_text: '',
|
||||
};
|
||||
this.set_search_text=props.set_search_text;
|
||||
}
|
||||
|
||||
on_change(event) {
|
||||
this.setState({
|
||||
search_text: event.target.value,
|
||||
});
|
||||
}
|
||||
|
||||
on_keypress(event) {
|
||||
if(event.key==='Enter')
|
||||
this.set_search_text(this.state.search_text);
|
||||
}
|
||||
|
||||
do_refresh() {
|
||||
this.setState({
|
||||
search_text: '',
|
||||
});
|
||||
this.set_search_text(null);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="control-bar aux-margin">
|
||||
<a className="refresh-btn" onClick={this.do_refresh.bind(this)}>最新树洞</a>
|
||||
|
||||
<input value={this.state.search_text} placeholder="搜索"
|
||||
onChange={this.on_change.bind(this)} onKeyPress={this.on_keypress.bind(this)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,6 @@
|
||||
box-shadow: 0 5px 20px #999;
|
||||
}
|
||||
|
||||
.left-container .centered-line {
|
||||
width: calc(100% - 2 * 50px);
|
||||
}
|
||||
.flow-item {
|
||||
flex: 0 0 600px;
|
||||
}
|
||||
@@ -19,20 +16,33 @@
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.left-container .centered-line,
|
||||
.left-container .aux-margin,
|
||||
.left-container .flow-item {
|
||||
margin-left: 50px;
|
||||
}
|
||||
.left-container .aux-margin {
|
||||
width: calc(100% - 2 * 50px);
|
||||
}
|
||||
|
||||
:not(.sidebar-flow-item).flow-item-row {
|
||||
@media screen and (max-width: 1200px) {
|
||||
.left-container .aux-margin,
|
||||
.left-container .flow-item {
|
||||
margin-left: 10px;
|
||||
}
|
||||
.left-container .aux-margin {
|
||||
width: calc(100% - 2 * 10px);
|
||||
}
|
||||
}
|
||||
|
||||
.left-container .flow-item-row {
|
||||
cursor: pointer;
|
||||
transition: margin-left 200ms ease-out;
|
||||
}
|
||||
:not(.sidebar-flow-item).flow-item-row:hover {
|
||||
.left-container .flow-item-row:hover {
|
||||
margin-left: -10px;
|
||||
}
|
||||
|
||||
:not(.sidebar-flow-item).flow-item-row {
|
||||
.left-container .flow-item-row {
|
||||
display: flex;
|
||||
overflow-x: hidden;
|
||||
align-items: flex-start;
|
||||
|
||||
88
src/Flows.js
88
src/Flows.js
@@ -1,9 +1,11 @@
|
||||
import React, {Component} from 'react';
|
||||
import {Time, CenteredLine} from './Common.js';
|
||||
import './Flows.css';
|
||||
import LazyLoad from 'react-lazyload';
|
||||
|
||||
const IMAGE_BASE='http://www.pkuhelper.com/services/pkuhole/images/';
|
||||
const AUDIO_BASE='http://www.pkuhelper.com/services/pkuhole/audios/';
|
||||
const SEARCH_PAGESIZE=50;
|
||||
|
||||
function Reply(props) {
|
||||
return (
|
||||
@@ -20,7 +22,7 @@ function Reply(props) {
|
||||
function ReplyPlaceholder(props) {
|
||||
return (
|
||||
<div className="box">
|
||||
正在加载 {props.count} 条回复
|
||||
加载中
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -29,8 +31,8 @@ function FlowItem(props) {
|
||||
return (
|
||||
<div className="flow-item box">
|
||||
<div className="box-header">
|
||||
{parseInt(props.info.likenum, 10) && <span className="box-header-badge">{props.info.likenum}★</span>}
|
||||
{parseInt(props.info.reply, 10) && <span className="box-header-badge">{props.info.reply} 回复</span>}
|
||||
{!!parseInt(props.info.likenum, 10) && <span className="box-header-badge">{props.info.likenum}★</span>}
|
||||
{!!parseInt(props.info.reply, 10) && <span className="box-header-badge">{props.info.reply} 回复</span>}
|
||||
<span className="box-id">#{props.info.pid}</span>
|
||||
<Time stamp={props.info.timestamp} />
|
||||
</div>
|
||||
@@ -49,15 +51,20 @@ class FlowItemRow extends Component {
|
||||
reply_loading: false,
|
||||
};
|
||||
this.info=props.info;
|
||||
if(parseInt(props.info.reply,10)) {
|
||||
this.state.reply_loading=true;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if(parseInt(this.info.reply,10)) {
|
||||
this.setState({
|
||||
reply_loading: true,
|
||||
});
|
||||
this.load_replies();
|
||||
}
|
||||
}
|
||||
|
||||
load_replies() {
|
||||
console.log('fetching reply',this.info.pid);
|
||||
fetch('http://www.pkuhelper.com:10301/pkuhelper/../services/pkuhole/api.php?action=getcomment&pid='+this.info.pid)
|
||||
fetch('http://www.pkuhelper.com:10301/services/pkuhole/api.php?action=getcomment&pid='+this.info.pid)
|
||||
.then((res)=>res.json())
|
||||
.then((json)=>{
|
||||
if(json.code!==0)
|
||||
@@ -80,7 +87,7 @@ class FlowItemRow extends Component {
|
||||
</div>
|
||||
)}}>
|
||||
<FlowItem info={this.info} />
|
||||
{this.state.reply_loading && <ReplyPlaceholder count={this.info.reply} />}
|
||||
{!!this.state.reply_loading && <ReplyPlaceholder count={this.info.reply} />}
|
||||
{this.state.replies.map((reply)=><Reply info={reply} key={reply.cid} />)}
|
||||
</div>
|
||||
);
|
||||
@@ -91,7 +98,11 @@ function FlowChunk(props) {
|
||||
return (
|
||||
<div className="flow-chunk">
|
||||
<CenteredLine text={props.title} />
|
||||
{props.list.map((info)=><FlowItemRow key={info.pid} info={info} callback={props.callback} />)}
|
||||
{props.list.map((info)=>(
|
||||
<LazyLoad key={info.pid} offset={500} height="15em">
|
||||
<FlowItemRow info={info} callback={props.callback} />
|
||||
</LazyLoad>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -100,7 +111,8 @@ export class Flow extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state={
|
||||
mode: props.mode,
|
||||
mode: props.search_text===null ? 'list' : 'search',
|
||||
search_param: props.search_text,
|
||||
loaded_pages: 0,
|
||||
chunks: [],
|
||||
loading: false,
|
||||
@@ -113,27 +125,49 @@ export class Flow extends Component {
|
||||
throw new Error('bad page');
|
||||
if(page===this.state.loaded_pages+1) {
|
||||
console.log('fetching page',page);
|
||||
if(this.state.mode==='list') {
|
||||
fetch('http://www.pkuhelper.com:10301/services/pkuhole/api.php?action=getlist&p='+page)
|
||||
.then((res)=>res.json())
|
||||
.then((json)=>{
|
||||
if(json.code!==0)
|
||||
throw new Error(json.code);
|
||||
this.setState((prev,props)=>({
|
||||
chunks: prev.chunks.concat([{
|
||||
title: 'Page '+page,
|
||||
data: json.data,
|
||||
}]),
|
||||
loading: false,
|
||||
}));
|
||||
})
|
||||
.catch((err)=>{
|
||||
console.trace(err);
|
||||
alert('load failed');
|
||||
});
|
||||
} else if(this.state.mode==='search') {
|
||||
fetch(
|
||||
'http://www.pkuhelper.com:10301/services/pkuhole/api.php?action=search'+
|
||||
'&pagesize='+SEARCH_PAGESIZE*page+
|
||||
'&keywords='+encodeURIComponent(this.state.search_param)
|
||||
)
|
||||
.then((res)=>res.json())
|
||||
.then((json)=>{
|
||||
if(json.code!==0)
|
||||
throw new Error(json.code);
|
||||
const finished=json.data.length<SEARCH_PAGESIZE;
|
||||
this.setState((prev,props)=>({
|
||||
chunks: [{
|
||||
title: 'Result for "'+this.state.search_param+'"',
|
||||
data: json.data,
|
||||
mode: finished ? 'search_finished' : 'search',
|
||||
}],
|
||||
loading: false,
|
||||
}));
|
||||
})
|
||||
}
|
||||
this.setState((prev,props)=>({
|
||||
loaded_pages: prev.loaded_pages+1,
|
||||
loading: true,
|
||||
}));
|
||||
fetch('http://www.pkuhelper.com:10301/pkuhelper/../services/pkuhole/api.php?action=getlist&p='+page)
|
||||
.then((res)=>res.json())
|
||||
.then((json)=>{
|
||||
if(json.code!==0)
|
||||
throw new Error(json.code);
|
||||
this.setState((prev,props)=>({
|
||||
chunks: prev.chunks.concat([{
|
||||
title: 'Page '+page,
|
||||
data: json.data,
|
||||
}]),
|
||||
loading: false,
|
||||
}));
|
||||
})
|
||||
.catch((err)=>{
|
||||
console.trace(err);
|
||||
alert('load failed');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -161,7 +195,7 @@ export class Flow extends Component {
|
||||
{this.state.chunks.map((chunk)=>(
|
||||
<FlowChunk title={chunk.title} list={chunk.data} key={chunk.title} callback={this.props.callback} />
|
||||
))}
|
||||
<CenteredLine text={this.state.loading ? 'Loading More...' : '© xmcp'} />
|
||||
<CenteredLine text={this.state.loading ? 'Loading...' : '© xmcp'} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -33,10 +33,10 @@
|
||||
|
||||
@media screen and (max-width: 1200px) {
|
||||
.sidebar {
|
||||
width: 600px;
|
||||
width: 550px;
|
||||
}
|
||||
.sidebar-on .sidebar {
|
||||
left: calc(100% - 600px);
|
||||
left: calc(100% - 550px);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
line-height: 2em;
|
||||
font-size: 1.5em;
|
||||
background-color: rgba(255,255,255,.8);
|
||||
padding: 0 .5em;
|
||||
padding: 0 50px;
|
||||
box-shadow: 0 0 25px #999;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
body {
|
||||
min-width: 700px;
|
||||
min-width: 620px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: '微软雅黑', 'Microsoft YaHei', sans-serif;
|
||||
background-color: black;
|
||||
background-image: url(/eriri_bg.jpg);
|
||||
background-size: cover;
|
||||
background-attachment: fixed;
|
||||
}
|
||||
|
||||
body::-webkit-scrollbar {
|
||||
@@ -25,5 +22,12 @@ a {
|
||||
text-decoration: none;
|
||||
color: #00c;
|
||||
cursor: pointer;
|
||||
margin: 0 .5em;
|
||||
padding: 0 .5em;
|
||||
}
|
||||
|
||||
input {
|
||||
padding: 0 1em;
|
||||
border-radius: 5px;
|
||||
border: 1px solid black;
|
||||
outline: none;
|
||||
}
|
||||
Reference in New Issue
Block a user