web端更新功能

This commit is contained in:
johlanse 2022-09-25 23:58:04 +08:00
parent 90efd5ae46
commit 5f93bcf721
28 changed files with 752 additions and 452 deletions

View File

@ -106,6 +106,8 @@ var (
config = Config{
Model: 1,
}
configPath = "./config/config.yml"
)
//go:embed config_default.yml
@ -154,7 +156,7 @@ func InitConfig(path string, restart func()) {
if path == "" {
path = "./config/config.yml"
}
configPath = path
pathDir := strings.TrimSuffix(path, "config.yml")
viper.SetConfigType("yaml")
viper.AddConfigPath(pathDir)
@ -177,6 +179,7 @@ func InitConfig(path string, restart func()) {
viper.SetDefault("scheme", "https://johlanse.github.io/study_xxqg/scheme.html?")
viper.SetDefault("special_min_score", 10)
viper.SetDefault("tg.custom_api", "https://api.telegram.org")
viper.AutomaticEnv()
err := viper.Unmarshal(&config, func(decoderConfig *mapstructure.DecoderConfig) {
})
@ -228,3 +231,23 @@ func InitConfig(path string, restart func()) {
func GetConfig() Config {
return config
}
// GetConfigFile
/* @Description:
* @return string
*/
func GetConfigFile() string {
file, err := os.ReadFile(configPath)
if err != nil {
return err.Error()
}
return string(file)
}
func SaveConfigFile(data string) error {
err := os.WriteFile(configPath, []byte(data), 0666)
if err != nil {
return err
}
return err
}

2
go.sum
View File

@ -176,6 +176,8 @@ github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALr
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.15.10 h1:Ai8UzuomSCDw90e1qNMtb15msBXsNpH6gzkkENQNcJo=
github.com/klauspost/compress v1.15.10/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=

View File

@ -6,6 +6,68 @@ import (
"github.com/johlanse/study_xxqg/conf"
)
func configFileGet() gin.HandlerFunc {
return func(ctx *gin.Context) {
level := ctx.GetInt("level")
if level != 1 {
ctx.JSON(200, Resp{
Code: 403,
Message: "",
Data: nil,
Success: false,
Error: "",
})
return
}
ctx.JSON(200, Resp{
Code: 200,
Message: "获取成功",
Data: conf.GetConfigFile(),
Success: true,
Error: "",
})
}
}
func configFileSet() gin.HandlerFunc {
return func(ctx *gin.Context) {
level := ctx.GetInt("level")
if level != 1 {
ctx.JSON(200, Resp{
Code: 403,
Message: "",
Data: nil,
Success: false,
Error: "",
})
return
}
var body map[string]string
_ = ctx.ShouldBindJSON(&body)
err := conf.SaveConfigFile(body["data"])
if err != nil {
ctx.JSON(200, Resp{
Code: 503,
Message: "",
Data: nil,
Success: false,
Error: err.Error(),
})
return
}
ctx.JSON(200, Resp{
Code: 200,
Message: "获取成功",
Data: conf.GetConfigFile(),
Success: true,
Error: "",
})
}
}
func configGet() gin.HandlerFunc {
return func(ctx *gin.Context) {
level := ctx.GetInt("level")

View File

@ -105,6 +105,8 @@ func RouterInit() *gin.Engine {
config.GET("", configGet())
config.POST("", configSet())
config.GET("/file", configFileGet())
config.POST("/file", configFileSet())
// 对用户管理的组
user := router.Group("/user", check())

View File

@ -1,15 +1,16 @@
{
"files": {
"main.css": "/static/xxqg/build/static/css/main.6f1e3389.css",
"main.js": "/static/xxqg/build/static/js/main.dc0d1572.js",
"main.css": "/static/xxqg/build/static/css/main.00055dce.css",
"main.js": "/static/xxqg/build/static/js/main.e7a49c3d.js",
"static/js/787.273d6ce9.chunk.js": "/static/xxqg/build/static/js/787.273d6ce9.chunk.js",
"static/media/codicon.ttf": "/static/xxqg/build/static/media/codicon.b797181c93b3755f4fa1.ttf",
"index.html": "/static/xxqg/build/index.html",
"main.6f1e3389.css.map": "/static/xxqg/build/static/css/main.6f1e3389.css.map",
"main.dc0d1572.js.map": "/static/xxqg/build/static/js/main.dc0d1572.js.map",
"main.00055dce.css.map": "/static/xxqg/build/static/css/main.00055dce.css.map",
"main.e7a49c3d.js.map": "/static/xxqg/build/static/js/main.e7a49c3d.js.map",
"787.273d6ce9.chunk.js.map": "/static/xxqg/build/static/js/787.273d6ce9.chunk.js.map"
},
"entrypoints": [
"static/css/main.6f1e3389.css",
"static/js/main.dc0d1572.js"
"static/css/main.00055dce.css",
"static/js/main.e7a49c3d.js"
]
}

View File

@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/static/xxqg/build/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/static/xxqg/build/logo192.png"/><link rel="manifest" href="/static/xxqg/build/manifest.json"/><title>Study XXQG</title><script defer="defer" src="/static/xxqg/build/static/js/main.dc0d1572.js"></script><link href="/static/xxqg/build/static/css/main.6f1e3389.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/static/xxqg/build/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/static/xxqg/build/logo192.png"/><link rel="manifest" href="/static/xxqg/build/manifest.json"/><title>Study XXQG</title><script defer="defer" src="/static/xxqg/build/static/js/main.e7a49c3d.js"></script><link href="/static/xxqg/build/static/css/main.00055dce.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -10,6 +10,8 @@ object-assign
http://jedwatson.github.io/classnames
*/
/*! @license DOMPurify 2.3.1 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/2.3.1/LICENSE */
/**
* @license qrcode.react
* Copyright (c) Paul O'Shannessy

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,8 @@
const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin');
module.exports = function override(config, env) {
config.plugins.push(new MonacoWebpackPlugin({
languages: ['json']
}));
return config;
}

View File

@ -13,9 +13,11 @@
"antd-mobile": "^5.10.2",
"axios": "^0.26.1",
"http-proxy-middleware": "^2.0.5",
"monaco-editor": "^0.34.0",
"qrcode.react": "^3.0.1",
"react": "17.x",
"react-dom": "17.x",
"react-monaco-editor": "^0.50.1",
"react-scripts": "5.0.1",
"react-uuid": "^1.0.2",
"typescript": "^4.6.3",
@ -46,7 +48,4 @@
]
},
"homepage": "/static/xxqg/build/"
}

View File

@ -1,10 +1,11 @@
import React, {Component} from 'react';
import React from 'react';
import './App.css';
import {Button, Dialog, Divider, Form, Input, List, Modal, NavBar, Popup, TextArea, Toast,} from "antd-mobile";
import {List, NavBar, Popup,} from "antd-mobile";
import {UnorderedListOutline} from "antd-mobile-icons";
import {ListItem} from "antd-mobile/es/components/list/list-item";
import {checkQrCode, getLog, getScore, getToken, getUsers, getLink, stopStudy, study, login, checkToken,getAbout,deleteUser, getExpiredUsers} from "./utils/api";
import QrCode from 'qrcode.react';
import {checkToken} from "./utils/api";
import Login from './compents/Login';
import Router from './compents/Router';
class App extends React.Component<any, any> {
@ -23,12 +24,54 @@ class App extends React.Component<any, any> {
this.setState({
level: level
})
this.items.push(
{
"key":"config",
"text":"配置管理"
}
)
this.items.push(
{
"key":"log",
"text":"日志查看"
}
)
this.items.push(
{
"key":"other",
"text":"其他功能"
}
)
this.items.map((value, index, array)=>{
if (value.key === "config" || value.key === "log"){
this.elements.push(
<ListItem disabled={this.state.level === 2} onClick={() => {
this.setState({"index": value.key})
}}>{value.text}</ListItem>
);
}else {
this.elements.push(
<ListItem onClick={() => {
this.setState({"index": value.key})
}}>{value.text}</ListItem>
);
}
return true;
})
}
set_login = () => {
this.setState({
is_login: true
})
this.check_token()
window.location.reload()
}
check_token = () => {
@ -53,8 +96,22 @@ class App extends React.Component<any, any> {
componentDidMount() {
this.check_token()
}
elements:any = []
items = [
{
"key":"login",
"text":"添加用户"
},
{
"key":"user_list",
"text":"用户管理"
}
]
render() {
@ -74,11 +131,11 @@ class App extends React.Component<any, any> {
})}>
<h1 style={{textAlign: "center"}}>XXQG</h1>
<List>
<ListItem onClick={()=>{this.setState({"index":"login"})}}></ListItem>
<ListItem onClick={()=>{this.setState({"index":"user_list"})}}></ListItem>
<ListItem onClick={()=>{this.setState({"index":"config"})}}></ListItem>
<ListItem onClick={()=>{this.setState({"index":"log"})}}></ListItem>
<ListItem onClick={()=>{this.setState({"index":"help"})}}></ListItem>
{this.elements}
<ListItem onClick={() => {
window.localStorage.removeItem("xxqg_token")
this.setState({"index": "help"})
}}></ListItem>
<ListItem onClick={() => {
window.localStorage.removeItem("xxqg_token")
this.setState({
@ -97,7 +154,6 @@ class App extends React.Component<any, any> {
}
back = () => {
this.setState({
popup_visible: true,
@ -107,362 +163,4 @@ class App extends React.Component<any, any> {
}
class Login extends Component<any, any>{
constructor(props: any) {
super(props);
this.state = {
img : "你还未获取登录链接"
};
}
onFinish = (value:string)=>{
login(JSON.stringify(value)).then(resp => {
console.log(resp.message)
Dialog.alert({content: resp.message,closeOnMaskClick:false})
if (resp.success){
window.localStorage.setItem("xxqg_token",resp.data)
this.props.parent.set_login()
}
})
}
render() {
return <>
<Form
onFinish = {this.onFinish}
footer={
<Button block type='submit' color='primary' size='large'>
</Button>
}
>
<Form.Header><h1>XXQG </h1></Form.Header>
<Form.Item name='account' label='账号' rules={[{ required: true }]}>
<Input placeholder='请输入账号' />
</Form.Item>
<Form.Item name='password' label='密码' rules={[{ required: true }]}>
<Input placeholder='请输入密码' type={"password"}/>
</Form.Item>
</Form>
</>;
}
}
class Router extends Component<any, any>{
constructor(props: any) {
super(props);
this.state = {
img : "你还未获取登录链接"
};
}
isWechat = ()=> {
if (/MicroMessenger/i.test(window.navigator.userAgent)){
return "inline"
}else {
return "none"
}
}
render() {
let login = <>
<h2 style={{margin:10,color:"red",display:this.isWechat()}}></h2>
<Button onClick={this.click} color={"primary"} style={{margin:10,marginRight:10}} block></Button>
<QrCode style={{margin:10}} fgColor={"#000000"} size={200} value={this.state.img} />
</>;
let userList = <Users data={"12"} level={this.props.level}/>;
let config = <h1></h1>
let help = <Help />
let log = <Log />
if (this.props.data === "login"){
return login;
}else if (this.props.data === "user_list"){
return userList;
}else if (this.props.data === "help"){
return help;
} else if (this.props.data === "log"){
return log;
} else {
return config;
}
}
componentWillUnmount() {
if (this.state.check !== undefined){
clearInterval(this.state.check)
}
}
click = async () => {
let data = await getLink()
this.setState({
img: data.url
})
let check = setInterval(async ()=>{
let resp = await checkQrCode(data.code);
if (resp.success){
clearInterval(check)
console.log("登录成功")
console.log(resp.data)
let token = await getToken(resp.data.split("=")[1],data.sign)
console.log(token)
if (token.success){
Toast.show("登录成功")
}
}
},5000)
this.setState({
check: check
})
setTimeout(()=>{
clearInterval(check)
},1000*300)
let element = document.createElement("a");
element.href = "dtxuexi://appclient/page/study_feeds?url="+escape(data.url)
element.click()
}
}
class Log extends Component<any, any>{
constructor(props:any) {
super(props);
this.state = {
data : ""
}
}
reverse = ( str:string ):string=>{
return str.split("\n").reverse().join("\n").trim()
};
timer: any
componentDidMount() {
getLog().then(data=>{
this.setState({
data:this.reverse(data)
})
})
this.timer = setInterval(()=>{
getLog().then((data:string)=>{
this.setState({
data:this.reverse(data)
})
})
},30000)
}
componentWillUnmount() {
clearInterval(this.timer)
}
render() {
return <>
<TextArea style={{margin:10}} autoSize disabled={true} value={this.state.data}/>
</>
}
}
class Help extends Component<any, any> {
constructor(props: any) {
super(props);
this.state = {
about: ""
};
}
componentDidMount() {
getAbout().then((value)=>{
this.setState({
about:value.data
})
})
}
render() {
return <>
<h2 style={{margin:10}}><a href="https://github.com/johlanse/study_xxqg">https://github.com/johlanse/study_xxqg</a></h2>
<br/><h2 style={{margin:10}}>{this.state.about}</h2>
</>
}
}
class Users extends Component<any, any>{
constructor(props: any) {
super(props);
this.state = {
users:[],
expired_users:[]
};
}
componentDidMount() {
getUsers().then(users =>{
console.log(users)
this.setState({
users: users.data
})
})
getExpiredUsers().then(users => {
console.log(users)
if (users.data !== null){
this.setState({
expired_users: users.data
})
}
})
}
format = (value:any)=> {
const date = new Date(value*1000);
let y = date.getFullYear(),
m = date.getMonth() + 1,
d = date.getDate(),
h = date.getHours(),
i = date.getMinutes(),
s = date.getSeconds();
if (m < 10) { m = parseInt('0') + m; }
if (d < 10) { d = parseInt('0') + d; }
if (h < 10) { h = parseInt('0') + h; }
if (i < 10) { i = parseInt('0') + i; }
if (s < 10) { s = parseInt('0') + s; }
return y + '-' + m + '-' + d + ' ' + h + ':' + i + ':' + s;
}
getScore = (token:string,nick:string)=>{
getScore(token).then((data)=>{
console.log(data)
Modal.alert({
title: nick,
content: data.data,
closeOnMaskClick: true,
})
})
}
checkStudy = (is_study:boolean)=>{
if (is_study){
return "停止学习"
}else {
return "开始学习"
}
}
checkStudyColor = (is_study:boolean)=>{
if (is_study){
return "danger"
}else {
return "primary"
}
}
study = (uid:string,is_study:boolean) =>{
if (!is_study){
study(uid).then(()=>{
Toast.show("开始学习成功")
getUsers().then(users =>{
console.log(users)
this.setState({
users: users.data
})
})
})
}else {
stopStudy(uid).then(()=>{
Toast.show("已停止学习")
getUsers().then(users =>{
console.log(users)
this.setState({
users: users.data
})
})
})
}
}
delete_user = (uid:string,nick:string)=>{
Dialog.confirm({content:"你确定要删除用户"+nick+"吗?"}).then((confirm) => {
if (confirm){
deleteUser(uid).then((data) => {
if (data.success){
getUsers().then(users =>{
console.log(users)
if (users.data != null){
this.setState({
users: users.data
})
}
})
}else {
Dialog.show({content:data.error})
}
})
}
})
}
render() {
let elements = []
for (let i = 0; i < this.state.users.length; i++) {
console.log(this.props.level)
elements.push(
<>
<ListItem key={this.state.users[i].uid} style={{border:"blue soild 1px"}}>
<h3>{this.state.users[i].nick}</h3>
<h3>UID: {this.state.users[i].uid}</h3>
<h3>{this.format(this.state.users[i].login_time)}</h3>
<Button onClick={this.study.bind(this,this.state.users[i].uid,this.state.users[i].is_study)} color={this.checkStudyColor(this.state.users[i].is_study)} block={true}>
{this.checkStudy(this.state.users[i].is_study)}
</Button>
<br />
<Button onClick={this.getScore.bind(this,this.state.users[i].token,this.state.users[i].nick)} color={"success"} block={true}></Button>
<br />
<Button style={{display: this.props.level !== 1 ? "none" : "inline"}} onClick={this.delete_user.bind(this,this.state.users[i].uid,this.state.users[i].nick)} color={"danger"} block={true}></Button>
</ListItem>
<Divider />
</>
)
}
for (let i = 0; i < this.state.expired_users.length; i++) {
console.log(this.state.expired_users[i].uid)
elements.push(
<>
<ListItem key={this.state.expired_users[i].uid} style={{border:"blue soild 1px",backgroundColor:"#cdced0"}}>
<h3>{this.state.expired_users[i].nick}</h3>
<h3>UID: {this.state.expired_users[i].uid}</h3>
<h3>{this.format(this.state.expired_users[i].login_time)}</h3>
<Button style={{display: this.props.level !== 1 ? "none" : "inline"}} onClick={this.delete_user.bind(this,this.state.expired_users[i].uid,this.state.expired_users[i].nick)} color={"danger"} block={true}></Button>
</ListItem>
<Divider />
</>
)
}
if (this.state.users.length === 0){
elements.push(<>
<span style={{color:"red"}}></span>
</>)
}
return <List>{elements}</List>;
}
}
export default App;

View File

@ -0,0 +1,64 @@
import React, {Component} from "react";
import {getConfig, saveConfig} from "../utils/api";
import MonacoEditor from 'react-monaco-editor';
import {Button, Dialog, Toast} from "antd-mobile";
class Config extends Component<any, any>{
private monaco: React.RefObject<MonacoEditor>;
constructor(props: any) {
super(props);
this.monaco = React.createRef<MonacoEditor>()
this.state = {
config: ""
};
}
componentDidMount() {
getConfig().then((value)=>{
this.setState({
config:value.data
})
})
}
editorDidMount = (editor:any, monaco:any) => {
console.log('editorDidMount', editor);
editor.focus();
}
onChange = (newValue:any, e:any)=> {
}
onSave = ()=> {
// @ts-ignore
let data = this.monaco.current.editor?.getModel().getValue()
saveConfig(data).then(resp => {
if (resp.code === 200){
Toast.show("保存成功")
}else {
Dialog.show({content:resp.err})
}
})
}
render() {
const options = {
selectOnLineNumbers: true
};
return <>
<Button style={{margin:10,marginRight:30}} onClick={this.onSave} color={"primary"} block={true}></Button><br/>
<MonacoEditor
ref={this.monaco}
width={window.innerWidth}
height={window.innerHeight}
language="yaml"
theme="vs"
value={this.state.config}
options={options}
onChange={this.onChange}
editorDidMount={this.editorDidMount}
/>
</>
}
}
export default Config

View File

@ -0,0 +1,29 @@
import React, {Component} from "react";
import {getAbout} from "../utils/api";
class Help extends Component<any, any> {
constructor(props: any) {
super(props);
this.state = {
about: ""
};
}
componentDidMount() {
getAbout().then((value)=>{
this.setState({
about:value.data
})
})
}
render() {
return <>
<h2 style={{margin:10}}><a href="https://github.com/johlanse/study_xxqg">https://github.com/johlanse/study_xxqg</a></h2>
<br/><h2 style={{margin:10}}>{this.state.about}</h2>
</>
}
}
export default Help

View File

@ -0,0 +1,46 @@
import React, {Component} from "react";
import {getLog} from "../utils/api";
import {TextArea} from "antd-mobile";
class Log extends Component<any, any>{
constructor(props:any) {
super(props);
this.state = {
data : ""
}
}
reverse = ( str:string ):string=>{
return str.split("\n").reverse().join("\n").trim()
};
timer: any
componentDidMount() {
getLog().then(data=>{
this.setState({
data:this.reverse(data)
})
})
this.timer = setInterval(()=>{
getLog().then((data:string)=>{
this.setState({
data:this.reverse(data)
})
})
},30000)
}
componentWillUnmount() {
clearInterval(this.timer)
}
render() {
return <>
<TextArea style={{margin:10}} autoSize disabled={true} value={this.state.data}/>
</>
}
}
export default Log

View File

@ -0,0 +1,47 @@
import React, {Component} from "react";
import {login} from "../utils/api";
import {Button, Dialog, Form, Input} from "antd-mobile";
class Login extends Component<any, any>{
constructor(props: any) {
super(props);
this.state = {
img : "你还未获取登录链接"
};
}
onFinish = (value:string)=>{
login(JSON.stringify(value)).then(resp => {
console.log(resp.message)
Dialog.alert({content: resp.message,closeOnMaskClick:false})
if (resp.success){
window.localStorage.setItem("xxqg_token",resp.data)
this.props.parent.set_login()
}
})
}
render() {
return <>
<Form
onFinish = {this.onFinish}
footer={
<Button block type='submit' color='primary' size='large'>
</Button>
}
>
<Form.Header><h1>XXQG </h1></Form.Header>
<Form.Item name='account' label='账号' rules={[{ required: true }]}>
<Input placeholder='请输入账号' />
</Form.Item>
<Form.Item name='password' label='密码' rules={[{ required: true }]}>
<Input placeholder='请输入密码' type={"password"}/>
</Form.Item>
</Form>
</>;
}
}
export default Login

View File

@ -0,0 +1,20 @@
import React, {Component} from "react";
import {Button, Toast} from "antd-mobile";
import {restart} from "../utils/api";
class Other extends Component<any, any>{
onrestart = ()=>{
restart().then(r => {
});
Toast.show("重启完成")
}
render() {
return <>
<Button style={{margin:10,marginRight:30}} onClick={this.onrestart} color={"primary"} block={true}></Button><br/>
</>;
}
}
export default Other

View File

@ -0,0 +1,96 @@
import React, {Component} from "react";
import {Button, Toast} from "antd-mobile";
import QrCode from "qrcode.react";
import Users from "./User";
import Help from "./Help";
import Log from "./Log";
import {checkQrCode, getLink, getToken} from "../utils/api";
import Config from "./Config";
import Other from "./Other";
class Router extends Component<any, any>{
constructor(props: any) {
super(props);
this.state = {
img : "你还未获取登录链接"
};
}
isWechat = ()=> {
if (/MicroMessenger/i.test(window.navigator.userAgent)){
return "inline"
}else {
return "none"
}
}
render() {
let login = <>
<h2 style={{margin:10,color:"red",display:this.isWechat()}}></h2>
<Button onClick={this.click} color={"primary"} style={{margin:10,marginRight:10}} block></Button>
<QrCode style={{margin:10}} fgColor={"#000000"} size={200} value={this.state.img} />
</>;
let userList = <Users data={"12"} level={this.props.level}/>;
let config = <Config />
let help = <Help />
let log = <Log />
if (this.props.data === "login"){
return login;
}else if (this.props.data === "user_list"){
return userList;
}else if (this.props.data === "help"){
return help;
} else if (this.props.data === "log"){
return log;
}else if (this.props.data === "other") {
return <Other />
}
else {
return config;
}
}
componentWillUnmount() {
if (this.state.check !== undefined){
clearInterval(this.state.check)
}
}
click = async () => {
let data = await getLink()
this.setState({
img: data.url
})
let check = setInterval(async ()=>{
let resp = await checkQrCode(data.code);
if (resp.success){
clearInterval(check)
console.log("登录成功")
console.log(resp.data)
let token = await getToken(resp.data.split("=")[1],data.sign)
console.log(token)
if (token.success){
Toast.show("登录成功")
}
}
},5000)
this.setState({
check: check
})
setTimeout(()=>{
clearInterval(check)
},1000*300)
let element = document.createElement("a");
element.href = "dtxuexi://appclient/page/study_feeds?url="+escape(data.url)
element.click()
}
}
export default Router

View File

@ -0,0 +1,173 @@
import React, {Component} from "react";
import {deleteUser, getExpiredUsers, getScore, getUsers, stopStudy, study} from "../utils/api";
import {Button, Dialog, Divider, List, Modal, Toast} from "antd-mobile";
import {ListItem} from "antd-mobile/es/components/list/list-item";
class Users extends Component<any, any>{
constructor(props: any) {
super(props);
this.state = {
users:[],
expired_users:[]
};
}
componentDidMount() {
getUsers().then(users =>{
console.log(users)
this.setState({
users: users.data
})
})
getExpiredUsers().then(users => {
console.log(users)
if (users.data !== null){
this.setState({
expired_users: users.data
})
}
})
}
format = (value:any)=> {
const date = new Date(value*1000);
let y = date.getFullYear(),
m = date.getMonth() + 1,
d = date.getDate(),
h = date.getHours(),
i = date.getMinutes(),
s = date.getSeconds();
if (m < 10) { m = parseInt('0') + m; }
if (d < 10) { d = parseInt('0') + d; }
if (h < 10) { h = parseInt('0') + h; }
if (i < 10) { i = parseInt('0') + i; }
if (s < 10) { s = parseInt('0') + s; }
return y + '-' + m + '-' + d + ' ' + h + ':' + i + ':' + s;
}
getScore = (token:string,nick:string)=>{
getScore(token).then((data)=>{
console.log(data)
Modal.alert({
title: nick,
content: data.data,
closeOnMaskClick: true,
})
})
}
checkStudy = (is_study:boolean)=>{
if (is_study){
return "停止学习"
}else {
return "开始学习"
}
}
checkStudyColor = (is_study:boolean)=>{
if (is_study){
return "danger"
}else {
return "primary"
}
}
study = (uid:string,is_study:boolean) =>{
if (!is_study){
study(uid).then(()=>{
Toast.show("开始学习成功")
getUsers().then(users =>{
console.log(users)
this.setState({
users: users.data
})
})
})
}else {
stopStudy(uid).then(()=>{
Toast.show("已停止学习")
getUsers().then(users =>{
console.log(users)
this.setState({
users: users.data
})
})
})
}
}
delete_user = (uid:string,nick:string)=>{
Dialog.confirm({content:"你确定要删除用户"+nick+"吗?"}).then((confirm) => {
if (confirm){
deleteUser(uid).then((data) => {
if (data.success){
getUsers().then(users =>{
console.log(users)
if (users.data != null){
this.setState({
users: users.data
})
}
})
}else {
Dialog.show({content:data.error})
}
})
}
})
}
render() {
let elements = []
for (let i = 0; i < this.state.users.length; i++) {
console.log(this.props.level)
elements.push(
<>
<ListItem key={this.state.users[i].uid} style={{border:"blue soild 1px"}}>
<h3>{this.state.users[i].nick}</h3>
<h3>UID: {this.state.users[i].uid}</h3>
<h3>{this.format(this.state.users[i].login_time)}</h3>
<Button onClick={this.study.bind(this,this.state.users[i].uid,this.state.users[i].is_study)} color={this.checkStudyColor(this.state.users[i].is_study)} block={true}>
{this.checkStudy(this.state.users[i].is_study)}
</Button>
<br />
<Button onClick={this.getScore.bind(this,this.state.users[i].token,this.state.users[i].nick)} color={"success"} block={true}></Button>
<br />
<Button style={{display: this.props.level !== 1 ? "none" : "inline"}} onClick={this.delete_user.bind(this,this.state.users[i].uid,this.state.users[i].nick)} color={"danger"} block={true}></Button>
</ListItem>
<Divider />
</>
)
}
for (let i = 0; i < this.state.expired_users.length; i++) {
console.log(this.state.expired_users[i].uid)
elements.push(
<>
<ListItem key={this.state.expired_users[i].uid} style={{border:"blue soild 1px",backgroundColor:"#cdced0"}}>
<h3>{this.state.expired_users[i].nick}</h3>
<h3>UID: {this.state.expired_users[i].uid}</h3>
<h3>{this.format(this.state.expired_users[i].login_time)}</h3>
<Button style={{display: this.props.level !== 1 ? "none" : "inline"}} onClick={this.delete_user.bind(this,this.state.expired_users[i].uid,this.state.expired_users[i].nick)} color={"danger"} block={true}></Button>
</ListItem>
<Divider />
</>
)
}
if (this.state.users.length === 0){
elements.push(<>
<span style={{color:"red"}}></span>
</>)
}
return <List>{elements}</List>;
}
}
export default Users

View File

@ -73,6 +73,22 @@ export async function getUsers(){
return resp.data
}
export async function getConfig() {
let resp = await http.get(base+"/config/file");
return resp.data;
}
export async function restart() {
let resp = await http.post(base+"/restart");
return resp.data;
}
export async function saveConfig(data) {
let resp = await http.post(base+"/config/file",{
"data":data
});
return resp.data;
}
export async function getExpiredUsers(){
let resp = await http.get(base+"/user/expired");

View File

@ -5999,6 +5999,11 @@ mkdirp@^0.5.5, mkdirp@~0.5.1:
dependencies:
minimist "^1.2.6"
monaco-editor@^0.34.0:
version "0.34.0"
resolved "https://registry.yarnpkg.com/monaco-editor/-/monaco-editor-0.34.0.tgz#b1749870a1f795dbfc4dc03d8e9b646ddcbeefa7"
integrity sha512-VF+S5zG8wxfinLKLrWcl4WUizMx+LeJrG4PM/M78OhcwocpV0jiyhX/pG6Q9jIOhrb/ckYi6nHnaR5OojlOZCQ==
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
@ -7185,6 +7190,13 @@ react-is@^17.0.1, react-is@^17.0.2:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
react-monaco-editor@^0.50.1:
version "0.50.1"
resolved "https://registry.yarnpkg.com/react-monaco-editor/-/react-monaco-editor-0.50.1.tgz#3b68ce03e4b316f435b9bcd8ec13ffd4c050c605"
integrity sha512-qvYdJhQdkPIrPDMxCrEl0T2x9TfBB+aUbrpFv69FwoTybeyfAjxgJ219MYSsgn3c/g06BgyLX4ENrXM4niZ9ag==
dependencies:
prop-types "^15.8.1"
react-refresh@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046"