Browse Source

add login and attention

dev
xmcp 7 years ago
parent
commit
9b5154ea4d
  1. 5
      public/_redirects
  2. 2
      public/index.html
  3. 49
      public/static/fonts_1/icomoon.css
  4. 18
      public/static/fonts_1/icomoon.svg
  5. BIN
      public/static/fonts_1/icomoon.ttf
  6. BIN
      public/static/fonts_1/icomoon.woff
  7. 46
      src/App.js
  8. 4
      src/Common.css
  9. 1
      src/Flows.css
  10. 127
      src/Flows.js
  11. 5
      src/Sidebar.css
  12. 17
      src/Title.css
  13. 56
      src/Title.js
  14. 8
      src/UserAction.css
  15. 92
      src/UserAction.js
  16. 19
      src/index.css

5
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
/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

2
public/index.html

@ -6,6 +6,8 @@
<link rel="icon" href="%PUBLIC_URL%/static/favicon/256.png">
<meta name="format-detection" content="telephone=no">
<link rel="stylesheet" href="%PUBLIC_URL%/static/fonts_1/icomoon.css" />
<meta name="mobile-web-app-capable" content="yes">
<link rel="shortcut icon" href="%PUBLIC_URL%/static/favicon/256.png">
<link rel="manifest" href="%PUBLIC_URL%/static/manifest.json">

49
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";
}

18
public/static/fonts_1/icomoon.svg

@ -0,0 +1,18 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Generated by IcoMoon</metadata>
<defs>
<font id="icomoon" horiz-adv-x="1024">
<font-face units-per-em="1024" ascent="960" descent="-64" />
<missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" horiz-adv-x="512" d="" />
<glyph unicode="&#xe96b;" glyph-name="reply" d="M512 896c282.77 0 512-186.25 512-416 0-229.752-229.23-416-512-416-27.156 0-53.81 1.734-79.824 5.044-109.978-109.978-241.25-129.7-368.176-132.596v26.916c68.536 33.578 128 94.74 128 164.636 0 9.754-0.758 19.33-2.164 28.696-115.796 76.264-189.836 192.754-189.836 323.304 0 229.75 229.23 416 512 416z" />
<glyph unicode="&#xe975;" glyph-name="login-ok" d="M960 352l-288-288-96 96-64-64 160-160 352 352zM448 192h320v115.128c-67.22 39.2-156.308 66.11-256 74.26v52.78c70.498 39.728 128 138.772 128 237.832 0 159.058 0 288-192 288s-192-128.942-192-288c0-99.060 57.502-198.104 128-237.832v-52.78c-217.102-17.748-384-124.42-384-253.388h448v64z" />
<glyph unicode="&#xe98d;" glyph-name="login" d="M704 960c-176.73 0-320-143.268-320-320 0-20.026 1.858-39.616 5.376-58.624l-389.376-389.376v-192c0-35.346 28.654-64 64-64h64v64h128v128h128v128h128l83.042 83.042c34.010-12.316 70.696-19.042 108.958-19.042 176.73 0 320 143.268 320 320s-143.27 320-320 320zM799.874 639.874c-53.020 0-96 42.98-96 96s42.98 96 96 96 96-42.98 96-96-42.98-96-96-96z" />
<glyph unicode="&#xe9d3;" glyph-name="attention" d="M256 832v-896l320 320 320-320v896zM768 960h-640v-896l64 64v768h576z" />
<glyph unicode="&#xe9d7;" glyph-name="star" d="M1024 562.95l-353.78 51.408-158.22 320.582-158.216-320.582-353.784-51.408 256-249.538-60.432-352.352 316.432 166.358 316.432-166.358-60.434 352.352 256.002 249.538zM512 206.502l-223.462-117.48 42.676 248.83-180.786 176.222 249.84 36.304 111.732 226.396 111.736-226.396 249.836-36.304-180.788-176.222 42.678-248.83-223.462 117.48z" />
<glyph unicode="&#xe9d9;" glyph-name="star-ok" d="M1024 562.95l-353.78 51.408-158.22 320.582-158.216-320.582-353.784-51.408 256-249.538-60.432-352.352 316.432 166.358 316.432-166.358-60.434 352.352 256.002 249.538z" />
<glyph unicode="&#xea09;" glyph-name="help" d="M448 256h128v-128h-128zM704 704c35.346 0 64-28.654 64-64v-192l-192-128h-128v64l192 128v64h-320v128h384zM512 864c-111.118 0-215.584-43.272-294.156-121.844s-121.844-183.038-121.844-294.156c0-111.118 43.272-215.584 121.844-294.156s183.038-121.844 294.156-121.844c111.118 0 215.584 43.272 294.156 121.844s121.844 183.038 121.844 294.156c0 111.118-43.272 215.584-121.844 294.156s-183.038 121.844-294.156 121.844zM512 960v0c282.77 0 512-229.23 512-512s-229.23-512-512-512c-282.77 0-512 229.23-512 512s229.23 512 512 512z" />
<glyph unicode="&#xea2e;" glyph-name="refresh" d="M889.68 793.68c-93.608 102.216-228.154 166.32-377.68 166.32-282.77 0-512-229.23-512-512h96c0 229.75 186.25 416 416 416 123.020 0 233.542-53.418 309.696-138.306l-149.696-149.694h352v352l-134.32-134.32zM928 448c0-229.75-186.25-416-416-416-123.020 0-233.542 53.418-309.694 138.306l149.694 149.694h-352v-352l134.32 134.32c93.608-102.216 228.154-166.32 377.68-166.32 282.77 0 512 229.23 512 512h-96z" />
</font></defs></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
public/static/fonts_1/icomoon.ttf

Binary file not shown.

BIN
public/static/fonts_1/icomoon.woff

Binary file not shown.

46
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 {TokenCtx} from './UserAction';
class App extends Component {
constructor(props) {
@ -9,9 +10,10 @@ class App extends Component {
this.state={
sidebar_title: null,
sidebar_content: null,
mode: 'list', // list, single, search
mode: 'list', // list, single, search, attention
search_text: null,
flow_render_key: +new Date(),
token: localStorage['TOKEN']||null,
};
this.show_sidebar_bound=this.show_sidebar.bind(this);
this.set_mode_bound=this.set_mode.bind(this);
@ -34,23 +36,35 @@ class App extends Component {
render() {
return (
<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">
<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>
);
}
}

4
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%;
}

1
src/Flows.css

@ -112,7 +112,6 @@ p.img img {
}
.box-id {
font-family: Consolas, Courier, monospace;
opacity: .6;
}

127
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>&nbsp;
<code className="box-id">#{props.info.cid}</code>&nbsp;
<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>&nbsp;
{!!parseInt(props.info.likenum,10) &&
<span className="box-header-badge">
{props.info.likenum}&nbsp;
<span className={'icon icon-'+(props.attention ? 'star-ok' : 'star')} />
</span>
}
{!!parseInt(props.info.reply,10) &&
<span className="box-header-badge">
{props.info.reply}&nbsp;
<span className="icon icon-reply" />
</span>
}
<code className="box-id">#{props.info.pid}</code>&nbsp;
<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;

5
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;
}
}

17
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;

56
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>
&nbsp;
<input value={this.state.search_text} placeholder="搜索 或 #PID"
onChange={this.on_change_bound} onKeyPress={this.on_keypress_bound}
/>
&nbsp;
<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>
)
}
}

8
src/UserAction.css

@ -0,0 +1,8 @@
.login-form form p {
margin: 1em 0;
text-align: center;
}
.login-form button {
min-width: 100px;
}

92
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>
)
}
}

19
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;
}
Loading…
Cancel
Save