From 9b5154ea4d411e54122774b380277a4d2f0ea451 Mon Sep 17 00:00:00 2001 From: xmcp Date: Fri, 24 Aug 2018 20:34:08 +0800 Subject: [PATCH] add login and attention --- public/_redirects | 5 +- public/index.html | 2 + public/static/fonts_1/icomoon.css | 49 +++++++++++ public/static/fonts_1/icomoon.svg | 18 ++++ public/static/fonts_1/icomoon.ttf | Bin 0 -> 2280 bytes public/static/fonts_1/icomoon.woff | Bin 0 -> 2356 bytes src/App.js | 46 +++++++---- src/Common.css | 4 +- src/Flows.css | 1 - src/Flows.js | 127 +++++++++++++++++++---------- src/Sidebar.css | 5 +- src/Title.css | 17 ++-- src/Title.js | 56 +++++++++---- src/UserAction.css | 8 ++ src/UserAction.js | 92 +++++++++++++++++++++ src/index.css | 19 +++++ 16 files changed, 354 insertions(+), 95 deletions(-) create mode 100644 public/static/fonts_1/icomoon.css create mode 100644 public/static/fonts_1/icomoon.svg create mode 100644 public/static/fonts_1/icomoon.ttf create mode 100644 public/static/fonts_1/icomoon.woff create mode 100644 src/UserAction.css create mode 100644 src/UserAction.js diff --git a/public/_redirects b/public/_redirects index 7e17922c..ac0520bf 100644 --- a/public/_redirects +++ b/public/_redirects @@ -1,2 +1,3 @@ -/api_proxy/* http://www.pkuhelper.com:10301/services/pkuhole/:splat 200 -/audio_proxy/* http://www.pkuhelper.com:10301/services/pkuhole/audios/:splat 200 \ No newline at end of file +/api_proxy/* http://www.pkuhelper.com/services/pkuhole/:splat 200 +/audio_proxy/* http://www.pkuhelper.com/services/pkuhole/audios/:splat 200 +/login_proxy/* http://www.pkuhelper.com/services/login/:splat 200 \ No newline at end of file diff --git a/public/index.html b/public/index.html index 4a9aa3cb..bccb22a1 100644 --- a/public/index.html +++ b/public/index.html @@ -6,6 +6,8 @@ + + diff --git a/public/static/fonts_1/icomoon.css b/public/static/fonts_1/icomoon.css new file mode 100644 index 00000000..b391fb6d --- /dev/null +++ b/public/static/fonts_1/icomoon.css @@ -0,0 +1,49 @@ +@font-face { + font-family: 'icomoon'; + src: + url('icomoon.ttf?4yzqd4') format('truetype'), + url('icomoon.woff?4yzqd4') format('woff'), + url('icomoon.svg?4yzqd4#icomoon') format('svg'); + font-weight: normal; + font-style: normal; +} + +.icon { + /* use !important to prevent issues with browser extensions that change fonts */ + /*noinspection CssNoGenericFontName*/ + font-family: 'icomoon' !important; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + + /* Better Font Rendering =========== */ + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.icon-reply:before { + content: "\e96b"; +} +.icon-login-ok:before { + content: "\e975"; +} +.icon-login:before { + content: "\e98d"; +} +.icon-attention:before { + content: "\e9d3"; +} +.icon-star:before { + content: "\e9d7"; +} +.icon-star-ok:before { + content: "\e9d9"; +} +.icon-help:before { + content: "\ea09"; +} +.icon-refresh:before { + content: "\ea2e"; +} diff --git a/public/static/fonts_1/icomoon.svg b/public/static/fonts_1/icomoon.svg new file mode 100644 index 00000000..14934e90 --- /dev/null +++ b/public/static/fonts_1/icomoon.svg @@ -0,0 +1,18 @@ + + + +Generated by IcoMoon + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/static/fonts_1/icomoon.ttf b/public/static/fonts_1/icomoon.ttf new file mode 100644 index 0000000000000000000000000000000000000000..af51e902d892d0396aeede954b3b9d6e66800778 GIT binary patch literal 2280 zcma)7U2GIp6h3F}?oPLb)`jk@-FCP8KTBKIZFi@OP;tbh0xb|BDGvtRrCqj^c6aHI zq2j|NCXEq@JeiP~riltZ7(-0($;64q$b&v_6JQbgPpu((Y?hG)CHF_sA_dEaR z-h0kmKmce14H#sP9D8o4W1HAZoGFsmv~) ze&_NxXNkT6;JP(kE=_Hlxg-L3&Xat2ni%5O><6Mr4vFcx#q%kkSm61I=_sJ?l2dX9<&=^y z?8U;c05c~}UQE-tc=E(dXS#F8&YdT+*=+6*;y^q;*uIrZm==wk8y}yT7#}|ui85hF zuq)(t3*Euvd9g$6NhFSDGJ}@TTo`4`^U>)HcK~=%76a&xDioL}!83@*cv}8WDd4v< z^hcR){8m)gu=r3%b?EIeWsI?eEbreh%L&HDlviIFt%O6UW6{y^5>`9`>E-@`tTPuGauOrl9T+q9dqVj6v&F4j*9Q74^QK||RRqdgs3Mx|{Xs8;bs6EhBvs%sly+h}H zovb>^F4=}#ktN+MiDE&15qnV#xWHIlT{Tt_Mv$Hn1OtBUySY)XZ`|w?ayfj-_$s$y ztX;c?#SPh7_Br}uyQeA*HHE*_sfqpIEyWpbDE$8kssp%_;R zN>nLWXCT_A-pT7VAo0mQg~G7&FzlL*D}ztfwzHH`*EEFgq^{|PuJ@(WW3gB$ECqU7 zX!Hi8a3~fVOQ-w(UE*v72S(5YHQB9eDm`oe(^pfJqSamwpEDM=&(iE&9ip(0_R4C{ zN6nG5+B9daJ0M1V;`T?4%@;2hs9u`3PGN7x`}>NDvdfB5v|2Ya(0?Qr5514Yl~gmw zSX*f_V8K*xZ-3hsD`~5%TfK>3SHv_nt~GJU*s0BH$E+Xp{ruYN9V352n|TEQ-2V8( zZ=cNe-+_MkW-aqUHvFG$77z{k5~pcwu_PAHG&5<(t2r>|b4{~D!f+Zsg$>~V@jolyt-HhDUL;RyQZiPI2bY`+PSF2Uw3`{}|=IE_K zb)r1KaHdv`3}&)0L47z63#32-@-qk-$Zl%2Df4K#vNT(or=Vp@bO~mm1oN9}j@GJ+ zkxIE*o-Zwyry{48BL^mHhbXrUjPlrv6um-jsyw3-EYdthyi>4D-vfLBhpYuqj@B*h IJe8;L-=;>Qng9R* literal 0 HcmV?d00001 diff --git a/public/static/fonts_1/icomoon.woff b/public/static/fonts_1/icomoon.woff new file mode 100644 index 0000000000000000000000000000000000000000..f43f6ec5f77b8741c8686b53224eb0fee2e74b68 GIT binary patch literal 2356 zcma)7U2GIp6h3EWcc`pam3?@?gMS+GR^=cT0bi ziVu^RG)5ruMAF1GO;mU>hM3^16Mv!)`eY*E!D!^g;;TMT%dF?#8DJJ_^iIy)?|$dp zbI-l^oS8R|4h;bUHE#}@Sl{rwHDYd#A%>qR z^{j>Yo3BEslXMJs(wkXzf(s~acm}o+(?DE!?cCbJ+Lg5**KV)fx#zu?u05#L$bvj= zs=?Y_d0Dj%%^5eMwldBmn5OwIK&GrXE z7?Ay~QZq`PSTw14IFi(TsOFMNQc0tdRCD=#m>&>e^5m&YDQcHaot$htrUA z$sR`Rjm7$!w{nfBN5bbvMn*?RM$U&LqOh~QE$DU&9qlJ_Qj63Xj~`2?`z$5f14YZ@ zG3X9=((R%m1<)N)X<;4&k02K1VfkC-fZvMH9}x}n*Mhc+g$D*|{qGE`!=e~hlmiD8 zB`%7?>Z>mgl|w-^uwdI2=0Zu28d^Fum{X6)fq;BO%?%z}3aOre{8CSE#u4o%nf(Y<`>y@bM^XlEUcR!ym9|#{O;ZO&HF4a%DYGY zo!+1J`J^NIoIJXFk+S*#P{m``AH#k*h*C_=s}VJC-GOA^dMnS@fXqAl7V-nm%dm4c zruIFN+s;%*L)Q^H5{7P=hS8l$4M(HFkR0f0pw<BXNjn}EHs8FGr+le9 zI#qlt*3(^3)!o(_1*>$^y*)>xvEVfgs9QVg+qgb)z@l-WFz) z&FggxO?F!I++q2HzMo!uy=Cw(XrhFmcKhQGzj@r-UkCO-qFm;U=J0PeOCW0WCZ?`! zu{0LPrm?X@bO+`TwBD`Q@H~qc+HuexdY~x1yKfVX_r*V0SX(o+@1;S+;i-e!Iv5WBAHg8wr3W2N%KRB@ITEm5EgFa<@J-PCfZTA2@*OO?`WalSMjKD`(|I95GOv87>%uRTwz im&r|qM^uD)>c@#Y4U6 -
- - <div className="left-container"> - <Flow key={this.state.flow_render_key} show_sidebar={this.show_sidebar_bound} - mode={this.state.mode} search_text={this.state.search_text} - /> - <br /> - </div> - <Sidebar do_close={()=>{ + <TokenCtx.Provider value={{ + value: this.state.token, + set_value: (x)=>{ + localStorage['TOKEN']=x||''; this.setState({ - sidebar_content: null, + token: x, }); - }} content={this.state.sidebar_content} title={this.state.sidebar_title} /> - </div> + }, + }}> + <div> + <div className="bg-img" style={{ + backgroundImage: 'url('+(localStorage['REPLACE_ERIRI_WITH_URL'] || 'static/eriri_bg.jpg')+')' + }} /> + <Title show_sidebar={this.show_sidebar_bound} set_mode={this.set_mode_bound} /> + <div className="left-container"> + <TokenCtx.Consumer>{(token)=>( + <Flow key={this.state.flow_render_key} show_sidebar={this.show_sidebar_bound} + mode={this.state.mode} search_text={this.state.search_text} token={token.value} + /> + )}</TokenCtx.Consumer> + <br /> + </div> + <Sidebar do_close={()=>{ + this.setState({ + sidebar_content: null, + }); + }} content={this.state.sidebar_content} title={this.state.sidebar_title} /> + </div> + </TokenCtx.Provider> ); } } diff --git a/src/Common.css b/src/Common.css index eac22c50..0f3bb043 100644 --- a/src/Common.css +++ b/src/Common.css @@ -15,12 +15,12 @@ } .centered-line::before { - right: 0.5em; + right: 1em; margin-left: -50%; } .centered-line::after { - left: 0.5em; + left: 1em; margin-right: -50%; } diff --git a/src/Flows.css b/src/Flows.css index 013a9944..a5d26283 100644 --- a/src/Flows.css +++ b/src/Flows.css @@ -112,7 +112,6 @@ p.img img { } .box-id { - font-family: Consolas, Courier, monospace; opacity: .6; } diff --git a/src/Flows.js b/src/Flows.js index 446dc150..783609c7 100644 --- a/src/Flows.js +++ b/src/Flows.js @@ -4,10 +4,11 @@ import {Time, TitleLine, HighlightedText} from './Common.js'; import './Flows.css'; import LazyLoad from 'react-lazyload'; import {AudioWidget} from './AudioWidget.js'; +import {TokenCtx} from './UserAction'; const IMAGE_BASE='http://www.pkuhelper.com/services/pkuhole/images/'; const AUDIO_BASE='/audio_proxy/'; -const API_BASE=window.location.protocol==='https:' ? '/api_proxy' : 'http://www.pkuhelper.com:10301/services/pkuhole'; +const API_BASE=window.location.protocol==='https:' ? '/api_proxy' : 'http://www.pkuhelper.com/services/pkuhole'; const SEARCH_PAGESIZE=50; const CLICKABLE_TAGS={a: true, audio: true}; @@ -21,7 +22,7 @@ function Reply(props) { backgroundColor: props.info._display_color, } : null}> <div className="box-header"> - <span className="box-id">#{props.info.cid}</span>  + <code className="box-id">#{props.info.cid}</code>  <Time stamp={props.info.timestamp} /> </div> <HighlightedText text={props.info.text} color_picker={props.color_picker} /> @@ -34,9 +35,19 @@ function FlowItem(props) { <div className="flow-item box"> {parseInt(props.info.pid,10)>window.LATEST_POST_ID && <div className="flow-item-dot" /> } <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>} - <span className="box-id">#{props.info.pid}</span>  + {!!parseInt(props.info.likenum,10) && + <span className="box-header-badge"> + {props.info.likenum}  + <span className={'icon icon-'+(props.attention ? 'star-ok' : 'star')} /> + </span> + } + {!!parseInt(props.info.reply,10) && + <span className="box-header-badge"> + {props.info.reply}  + <span className="icon icon-reply" /> + </span> + } + <code className="box-id">#{props.info.pid}</code>  <Time stamp={props.info.timestamp} /> </div> <HighlightedText text={props.info.text} color_picker={props.color_picker} /> @@ -53,6 +64,7 @@ class FlowItemRow extends PureComponent { replies: [], reply_status: 'done', info: props.info, + attention: false, }; this.color_picker=new ColorPicker(); } @@ -68,11 +80,16 @@ class FlowItemRow extends PureComponent { this.setState({ reply_status: 'loading', }); - fetch(API_BASE+'/api.php?action=getcomment&pid='+this.state.info.pid) + const token_param=this.props.token ? '&token='+this.props.token : ''; + fetch( + API_BASE+'/api.php?action=getcomment'+ + '&pid='+this.state.info.pid+ + token_param + ) .then((res)=>res.json()) .then((json)=>{ if(json.code!==0) - throw new Error(json.code); + throw new Error(json); const replies=json.data .sort((a,b)=>{ return parseInt(a.timestamp,10)-parseInt(b.timestamp,10); @@ -86,6 +103,7 @@ class FlowItemRow extends PureComponent { info: Object.assign({}, prev.info, { reply: ''+replies.length, }), + attention: !!json.attention, reply_status: 'done', }),callback); }) @@ -106,9 +124,9 @@ class FlowItemRow extends PureComponent { <a onClick={()=>{ this.props.show_sidebar('帖子详情',<p className="box box-tip">加载中……</p>); this.load_replies(this.show_sidebar); - }}>更新回复</a> + }}>刷新回复</a> </div> - <FlowItem info={this.state.info} color_picker={this.color_picker} /> + <FlowItem info={this.state.info} color_picker={this.color_picker} attention={this.state.attention} /> {this.state.replies.map((reply)=>( <LazyLoad offset={500} height="5em" overflow={true} once={true}> <Reply key={reply.cid} info={reply} color_picker={this.color_picker} /> @@ -125,7 +143,7 @@ class FlowItemRow extends PureComponent { if(!CLICKABLE_TAGS[event.target.tagName.toLowerCase()]) this.show_sidebar(); }}> - <FlowItem info={this.state.info} color_picker={this.color_picker} /> + <FlowItem info={this.state.info} color_picker={this.color_picker} attention={this.state.attention} /> <div className="flow-reply-row"> {this.state.reply_status==='loading' && <div className="box box-tip">加载中</div>} {this.state.reply_status==='failed' && @@ -145,14 +163,16 @@ class FlowItemRow extends PureComponent { function FlowChunk(props) { return ( - <div className="flow-chunk"> - <TitleLine text={props.title} /> - {props.list.map((info)=>( - <LazyLoad key={info.pid} offset={500} height="15em" once={true} > - <FlowItemRow info={info} show_sidebar={props.show_sidebar} /> - </LazyLoad> - ))} - </div> + <TokenCtx.Consumer>{({value: token})=>( + <div className="flow-chunk"> + <TitleLine text={props.title} /> + {props.list.map((info)=>( + <LazyLoad key={info.pid} offset={500} height="15em" once={true} > + <FlowItemRow info={info} show_sidebar={props.show_sidebar} token={token} /> + </LazyLoad> + ))} + </div> + )}</TokenCtx.Consumer> ); } @@ -171,16 +191,30 @@ export class Flow extends PureComponent { } load_page(page) { + const failed=(err)=>{ + console.trace(err); + this.setState((prev,props)=>({ + loaded_pages: prev.loaded_pages-1, + loading_status: 'failed', + })); + }; + + const token_param=this.props.token ? '&token='+this.props.token : ''; + if(page>this.state.loaded_pages+1) throw new Error('bad page'); if(page===this.state.loaded_pages+1) { console.log('fetching page',page); if(this.state.mode==='list') { - fetch(API_BASE+'/api.php?action=getlist&p='+page) + fetch( + API_BASE+'/api.php?action=getlist'+ + '&p='+page+ + token_param + ) .then((res)=>res.json()) .then((json)=>{ if(json.code!==0) - throw new Error(json.code); + throw new Error(json); json.data.forEach((x)=>{ if(parseInt(x.pid,10)>(parseInt(localStorage['_LATEST_POST_ID'],10)||0)) localStorage['_LATEST_POST_ID']=x.pid; @@ -196,23 +230,18 @@ export class Flow extends PureComponent { loading_status: 'done', })); }) - .catch((err)=>{ - console.trace(err); - this.setState((prev,props)=>({ - loaded_pages: prev.loaded_pages-1, - loading_status: 'failed', - })); - }); + .catch(failed); } else if(this.state.mode==='search') { fetch( API_BASE+'/api.php?action=search'+ '&pagesize='+SEARCH_PAGESIZE*page+ - '&keywords='+encodeURIComponent(this.state.search_param) + '&keywords='+encodeURIComponent(this.state.search_param)+ + token_param ) .then((res)=>res.json()) .then((json)=>{ if(json.code!==0) - throw new Error(json.code); + throw new Error(json); const finished=json.data.length<SEARCH_PAGESIZE; this.setState({ chunks: [{ @@ -223,23 +252,18 @@ export class Flow extends PureComponent { loading_status: 'done', }); }) - .catch((err)=>{ - console.trace(err); - this.setState((prev,props)=>({ - loaded_pages: prev.loaded_pages-1, - loading_status: 'failed', - })); - }); + .catch(failed); } else if(this.state.mode==='single') { const pid=parseInt(this.state.search_param.substr(1),10); fetch( API_BASE+'/api.php?action=getone'+ - '&pid='+pid + '&pid='+pid+ + token_param ) .then((res)=>res.json()) .then((json)=>{ if(json.code!==0) - throw new Error(json.code); + throw new Error(json); this.setState({ chunks: [{ title: 'PID = '+pid, @@ -249,13 +273,26 @@ export class Flow extends PureComponent { loading_status: 'done', }); }) - .catch((err)=>{ - console.trace(err); - this.setState((prev,props)=>({ - loaded_pages: prev.loaded_pages-1, - loading_status: 'failed', - })); - }); + .catch(failed); + } else if(this.state.mode==='attention') { + fetch( + API_BASE+'/api.php?action=getattention'+ + token_param + ) + .then((res)=>res.json()) + .then((json)=>{ + if(json.code!==0) + throw new Error(json); + this.setState({ + chunks: [{ + title: 'Attention List', + data: json.data, + }], + mode: 'attention_finished', + loading_status: 'done', + }); + }) + .catch(failed); } else { console.log('nothing to load'); return; diff --git a/src/Sidebar.css b/src/Sidebar.css index e8eca078..9e69f0ed 100644 --- a/src/Sidebar.css +++ b/src/Sidebar.css @@ -41,10 +41,11 @@ } @media screen and (max-width: 600px) { .sidebar { - width: calc(100% - 50px); + width: calc(100% - 25px); + padding: 1em .5em; } .sidebar-on .sidebar { - left: 50px; + left: 25px; } } diff --git a/src/Title.css b/src/Title.css index 804564b5..9a766832 100644 --- a/src/Title.css +++ b/src/Title.css @@ -10,13 +10,9 @@ margin-bottom: 1em; } -.title-bar a { - padding: 0 .5em; -} - .title { - font-size: 2em; - line-height: 3em; + font-size: 1.5em; + line-height: 4em; text-align: center; } @@ -26,13 +22,10 @@ line-height: 2em; } -.control-bar .refresh-btn { - flex: 0 0 100px; - color: black; - background-color: rgba(255,255,255,.5); - border-radius: 5px; +.control-btn { + flex: 0 0 2em; text-align: center; - border: 1px solid black; + color: black; } .control-bar input { flex: auto; diff --git a/src/Title.js b/src/Title.js index 9a19646a..a924b94d 100644 --- a/src/Title.js +++ b/src/Title.js @@ -1,4 +1,7 @@ import React, {Component, PureComponent} from 'react'; +import {LoginForm} from './UserAction'; +import {TokenCtx} from './UserAction'; + import './Title.css'; const HELP_TEXT=( @@ -10,7 +13,7 @@ const HELP_TEXT=( <li>在搜索框输入 #472865 等可以查看指定 ID 的树洞</li> <li>新的帖子会在左上角显示一个圆点</li> <li>请注意:使用 HTTPS 访问本站可能会<b>大幅减慢</b>加载速度</li> - <li>自定义背景图片请修改 localStorage['REPLACE_ERIRI_WITH_URL']</li> + <li>自定义背景图片请修改 <code>localStorage['REPLACE_ERIRI_WITH_URL']</code></li> </ul> <p>使用本网站时,您需要了解并同意:</p> <ul> @@ -50,6 +53,7 @@ class ControlBar extends PureComponent { this.on_change_bound=this.on_change.bind(this); this.on_keypress_bound=this.on_keypress.bind(this); this.do_refresh_bound=this.do_refresh.bind(this); + this.do_attention_bound=this.do_attention.bind(this); } componentDidMount() { @@ -69,8 +73,10 @@ class ControlBar extends PureComponent { } on_keypress(event) { - if(event.key==='Enter') - this.set_mode('search',this.state.search_text||null); + if(event.key==='Enter') { + const mode=this.state.search_text.startsWith('#') ? 'single' : 'search'; + this.set_mode(mode,this.state.search_text||null); + } } do_refresh() { @@ -81,20 +87,40 @@ class ControlBar extends PureComponent { this.set_mode('list',null); } + do_attention() { + window.scrollTo(0,0); + this.setState({ + search_text: '', + }); + this.set_mode('attention',null); + } + render() { return ( - <div className="control-bar"> - <a className="refresh-btn" onClick={this.do_refresh_bound}>最新树洞</a> -   - <input value={this.state.search_text} placeholder="搜索 或 #PID" - onChange={this.on_change_bound} onKeyPress={this.on_keypress_bound} - /> -   - <a onClick={()=>{this.props.show_sidebar( - '关于 P大树洞(非官方) 网页版', - HELP_TEXT - )}}>Help</a> - </div> + <TokenCtx.Consumer>{({value: token})=>( + <div className="control-bar"> + <a className="control-btn" onClick={this.do_refresh_bound}> + <span className="icon icon-refresh" /> + </a> + {!!token && + <a className="control-btn" onClick={this.do_attention_bound}> + <span className="icon icon-attention" /> + </a> + } + <input value={this.state.search_text} placeholder="搜索 或 #PID" + onChange={this.on_change_bound} onKeyPress={this.on_keypress_bound} + /> + <a className="control-btn" onClick={()=>{this.props.show_sidebar('登录',<LoginForm />)}}> + <span className={'icon icon-'+(token ? 'login-ok' : 'login')} /> + </a> + <a className="control-btn" onClick={()=>{this.props.show_sidebar( + '关于 P大树洞(非官方) 网页版', + HELP_TEXT + )}}> + <span className="icon icon-help" /> + </a> + </div> + )}</TokenCtx.Consumer> ) } } diff --git a/src/UserAction.css b/src/UserAction.css new file mode 100644 index 00000000..09b7e93c --- /dev/null +++ b/src/UserAction.css @@ -0,0 +1,8 @@ +.login-form form p { + margin: 1em 0; + text-align: center; +} + +.login-form button { + min-width: 100px; +} \ No newline at end of file diff --git a/src/UserAction.js b/src/UserAction.js new file mode 100644 index 00000000..702e11a9 --- /dev/null +++ b/src/UserAction.js @@ -0,0 +1,92 @@ +import React, {Component, PureComponent} from 'react'; + +import './UserAction.css'; + +const LOGIN_BASE=window.location.protocol==='https:' ? '/login_proxy' : 'http://www.pkuhelper.com/services/login'; + +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_login(event,set_token) { + event.preventDefault(); + this.setState({ + loading_status: 'loading', + }); + let data=new URLSearchParams(); + data.append('uid', this.username_ref.current.value); + data.append('password', this.password_ref.current.value); + fetch(LOGIN_BASE+'/login.php?platform=hole_xmcp_ml', { + method: 'POST', + body: data, + }) + .then((res)=>res.json()) + .then((json)=>{ + if(json.code!==0) + 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.trace(e); + }); + } + + render() { + return ( + <TokenCtx.Consumer>{(token)=> + <div className="login-form"> + <form onSubmit={(e)=>this.do_login(e,token.set_value)} className="box"> + <p>Token: <code>{token.value||'(null)'}</code></p> + <p> + <label> + 学号: + <input ref={this.username_ref} type="tel" /> + </label> + </p> + <p> + <label> + 密码: + <input ref={this.password_ref} type="password" /> + </label> + </p> + <p> + {this.state.loading_status==='loading' ? + <button disabled="disbled">正在登录……</button> : + <button type="submit">登录</button> + } + <button type="button" onClick={()=>{token.set_value(null);}}>退出</button> + </p> + </form> + <div className="box"> + <ul> + <li>我们不会记录您的密码和个人信息。</li> + <li><b>请勿泄露 Token</b>,它代表您的登录状态,与您的账户唯一对应且泄露后无法重置。</li> + <li>如果您不愿输入密码,可以直接修改 <code>localStorage['TOKEN']</code></li> + </ul> + </div> + </div> + }</TokenCtx.Consumer> + ) + } +} \ No newline at end of file diff --git a/src/index.css b/src/index.css index ccf653ab..97ae8856 100644 --- a/src/index.css +++ b/src/index.css @@ -31,6 +31,7 @@ input { border-radius: 5px; border: 1px solid black; outline: none; + line-height: 2em; } audio { @@ -40,4 +41,22 @@ audio { pre { white-space: pre-wrap; font-family: '微软雅黑', 'Microsoft YaHei', sans-serif; +} + +button, .button { + color: black; + background-color: rgba(255,255,255,.5); + border-radius: 5px; + text-align: center; + border: 1px solid black; + line-height: 2em; + margin: 0 .5em; +} + +button:disabled, .button:disabled { + background-color: rgba(128,128,128,.5); +} + +code { + font-family: Consolas, Courier, monospace; } \ No newline at end of file