Files
hole_thu_frontend/src/UserAction.js
2019-02-03 15:58:20 +08:00

371 lines
12 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import React, {Component, PureComponent} from 'react';
import {SafeTextarea} from './Common';
import md5 from 'md5';
import './UserAction.css';
import {API_BASE} from './Common';
const LOGIN_BASE=window.location.protocol==='https:' ? '/login_proxy' : 'http://www.pkuhelper.com/services/login';
const MAX_IMG_PX=2000;
const MAX_IMG_FILESIZE=256000;
const ISOP_APPKEY='0feb3a8a831e11e8933a0050568508a5';
const ISOP_APPCODE='0fec960a831e11e8933a0050568508a5';
const ISOP_SVCID='PERSON_BASE_INFO,STUDENT_SCORE,STUDENT_COURSE_TABLE,STUDENT_COURSE_TABLE_ROOM,CARD_BALANCE';
export const TokenCtx=React.createContext({
value: null,
set_value: ()=>{},
});
export class LoginForm extends Component {
constructor(props) {
super(props);
this.state={
loading_status: 'done',
};
this.username_ref=React.createRef();
this.password_ref=React.createRef();
}
do_sendcode() {
if(this.state.loading_status==='loading')
return;
let param=
'user='+this.username_ref.current.value+
'&svcId='+ISOP_SVCID+
'&appKey='+ISOP_APPKEY+
'&timestamp='+(+new Date());
fetch(
'https://isop.pku.edu.cn/svcpub/svc/oauth/validcode?'+param+
'&msg='+md5(param+ISOP_APPCODE),
{mode: 'no-cors'}
);
alert('如果学号存在,短信验证码将会发到您的手机上,请注意查收!');
}
do_login(set_token) {
if(this.state.loading_status==='loading')
return;
this.setState({
loading_status: 'loading',
});
let data=new URLSearchParams();
data.append('username', this.username_ref.current.value);
data.append('valid_code', this.password_ref.current.value);
data.append('isnewloginflow', 'true');
fetch(LOGIN_BASE+'/login.php?platform=hole_xmcp_ml', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: data,
})
.then((res)=>res.json())
.then((json)=>{
if(json.code!==0) {
if(json.msg) alert(json.msg);
throw new Error(json);
}
set_token(json.token);
alert(`成功以 ${json.name} 的身份登录`);
this.setState({
loading_status: 'done',
});
})
.catch((e)=>{
alert('登录失败');
this.setState({
loading_status: 'done',
});
console.error(e);
});
}
render() {
return (
<TokenCtx.Consumer>{(token)=>
<div className="login-form box">
{token.value ?
<div>
<p>
<b>您已登录</b>
<button type="button" onClick={()=>{token.set_value(null);}}>注销</button>
</p>
<p>
Token: <code>{token.value||'(null)'}</code> <br />
请勿泄露 Token它代表您的登录状态与您的账户唯一对应且泄露后无法重置
</p>
</div> :
<div>
<p>登录后可以使用关注回复等功能</p>
<p>
<label>
 学号&nbsp;
<input ref={this.username_ref} type="tel" />
</label>
<button type="button" disabled={this.state.loading_status==='loading'}
onClick={(e)=>this.do_sendcode()}>
发送验证码
</button>
</p>
<p>
<label>
验证码&nbsp;
<input ref={this.password_ref} type="tel" />
</label>
<button type="button" disabled={this.state.loading_status==='loading'}
onClick={(e)=>this.do_login(token.set_value)}>
登录
</button>
</p>
<p>
登录请求会被发送到北大统一验证接口和 PKU Helper 服务器 <br />
我们不会记录或使用您的登录信息
</p>
</div>
}
</div>
}</TokenCtx.Consumer>
)
}
}
export class ReplyForm extends Component {
constructor(props) {
super(props);
this.state={
text: '',
loading_status: 'done',
};
this.on_change_bound=this.on_change.bind(this);
this.area_ref=React.createRef();
}
on_change(value) {
this.setState({
text: value,
});
}
on_submit(event) {
event.preventDefault();
if(this.state.loading_status==='loading')
return;
this.setState({
loading_status: 'loading',
});
let data=new URLSearchParams();
data.append('action','docomment');
data.append('pid',this.props.pid);
data.append('text',this.state.text);
data.append('token',this.props.token);
fetch(API_BASE+'/api.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: data,
})
.then((res)=>res.json())
.then((json)=>{
if(json.code!==0) {
if(json.msg) alert(json.msg);
throw new Error(json);
}
this.setState({
loading_status: 'done',
text: '',
});
this.area_ref.current.clear();
this.props.on_complete();
})
.catch((e)=>{
console.error(e);
alert('回复失败\n树洞服务器经常抽风其实有可能已经回复上了不妨点“刷新回复”看一看');
this.setState({
loading_status: 'done',
});
});
}
render() {
return (
<form onSubmit={this.on_submit.bind(this)} className="reply-form box">
<SafeTextarea ref={this.area_ref} id={this.props.pid} on_change={this.on_change_bound} />
{this.state.loading_status==='loading' ?
<button disabled="disabled">
<span className="icon icon-loading" />
</button> :
<button type="submit">
<span className="icon icon-send" />
</button>
}
</form>
)
}
}
export class PostForm extends Component {
constructor(props) {
super(props);
this.state={
text: '',
loading_status: 'done',
};
this.img_ref=React.createRef();
this.area_ref=React.createRef();
this.on_change_bound=this.on_change.bind(this);
}
on_change(value) {
this.setState({
text: value,
});
}
do_post(text,img) {
let data=new URLSearchParams();
data.append('action','dopost');
data.append('text',this.state.text);
data.append('type',img ? 'image' : 'text');
data.append('token',this.props.token);
if(img)
data.append('data',img);
fetch(API_BASE+'/api.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: data,
})
.then((res)=>res.json())
.then((json)=>{
if(json.code!==0) {
if(json.msg) alert(json.msg);
throw new Error(json);
}
this.setState({
loading_status: 'done',
text: '',
});
this.area_ref.current.clear();
this.props.on_complete();
})
.catch((e)=>{
console.error(e);
alert('发表失败');
this.setState({
loading_status: 'done',
});
});
}
proc_img(file) { // http://pkuhole.chenpong.com/
return new Promise((resolve,reject)=>{
function return_url(url) {
const idx=url.indexOf(';base64,');
if(idx===-1)
throw new Error('img not base64 encoded');
resolve(url.substr(idx+8));
}
let reader=new FileReader();
reader.onload=((event)=>{ // check size
const url=event.target.result;
const image = new Image();
image.src=url;
image.onload=(()=>{
let width=image.width;
let height=image.height;
if(width>MAX_IMG_PX) {
height=height*MAX_IMG_PX/width;
width=MAX_IMG_PX;
}
if(height>MAX_IMG_PX) {
width=width*MAX_IMG_PX/height;
height=MAX_IMG_PX;
}
let canvas=document.createElement('canvas');
let ctx=canvas.getContext('2d');
canvas.width=width;
canvas.height=height;
ctx.drawImage(image,0,0,width,height);
for(let quality=.9;quality>0;quality-=0.1) {
const url=canvas.toDataURL('image/jpeg',quality);
console.log('quality',quality,'size',url.length);
if(url.length<=MAX_IMG_FILESIZE) {
console.log('chosen img quality',quality);
return return_url(url);
}
}
// else
alert('图片过大,无法上传');
reject('img too large');
});
});
reader.readAsDataURL(file);
});
}
on_submit(event) {
event.preventDefault();
if(this.state.loading_status==='loading')
return;
if(this.img_ref.current.files.length) {
this.setState({
loading_status: 'processing',
});
this.proc_img(this.img_ref.current.files[0])
.then((img)=>{
this.setState({
loading_status: 'loading',
});
this.do_post(this.state.text,img);
})
} else {
this.setState({
loading_status: 'loading',
});
this.do_post(this.state.text,null);
}
}
render() {
return (
<form onSubmit={this.on_submit.bind(this)} className="post-form box">
<div className="post-form-bar">
<label>
图片
<input ref={this.img_ref} type="file" accept="image/*"
{...this.state.loading_status!=='done' ? {disabled: true} : {}} />
</label>
{this.state.loading_status!=='done' ?
<button disabled="disabled">
<span className="icon icon-loading" />
&nbsp;正在{this.state.loading_status==='processing' ? '处理' : '上传'}
</button> :
<button type="submit">
<span className="icon icon-send" />
&nbsp;发表
</button>
}
</div>
<SafeTextarea ref={this.area_ref} id="new_post" on_change={this.on_change_bound} />
</form>
)
}
}