mirror of
https://github.com/suyiiyii/nonebot-bison.git
synced 2026-05-09 18:27:56 +08:00
update
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
"@types/react-dom": "^17.0.0",
|
||||
"antd": "^4.16.13",
|
||||
"axios": "^0.21.4",
|
||||
"lodash": "^4.17.21",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-router-dom": "^5.3.0",
|
||||
@@ -24,7 +25,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build && mv build ../src/plugins/nonebot_hk_reporter/admin_page/dist",
|
||||
"build": "react-scripts build && mv -f build ../src/plugins/nonebot_hk_reporter/admin_page/dist",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
@@ -47,6 +48,7 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash": "^4.14.175",
|
||||
"@types/react-router-dom": "^5.3.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { HashRouter as Router, Route, Switch } from 'react-router-dom';
|
||||
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
|
||||
import './App.css';
|
||||
import { LoginContext, loginContextDefault, GlobalConfContext } from './utils/context';
|
||||
import { LoginStatus, GlobalConf, AllPlatformConf } from './utils/type';
|
||||
@@ -41,7 +41,7 @@ function App() {
|
||||
<LoginContext.Provider value={{login: loginStatus, save}}>
|
||||
<GlobalConfContext.Provider value={globalConf}>
|
||||
{ globalConf.loaded &&
|
||||
<Router>
|
||||
<Router basename="/hk_reporter">
|
||||
<Switch>
|
||||
<Route path="/auth/:code">
|
||||
<Auth />
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import axios from "axios";
|
||||
import { GlobalConf, TokenResp, SubscribeResp, TargetNameResp } from "../utils/type";
|
||||
import { GlobalConf, TokenResp, SubscribeResp, TargetNameResp, CreateSubscribeReq } from "../utils/type";
|
||||
import { baseUrl } from './utils';
|
||||
|
||||
export async function getGlobalConf(): Promise<GlobalConf> {
|
||||
@@ -21,3 +21,8 @@ export async function getTargetName(platformName: string, target: string): Promi
|
||||
const res = await axios.get(`${baseUrl}target_name`, {params: {platformName, target}});
|
||||
return res.data;
|
||||
}
|
||||
|
||||
export async function addSubscribe(groupNumber: string, req: CreateSubscribeReq) {
|
||||
const res = await axios.post(`${baseUrl}subs`, req, {params: {groupNumber}})
|
||||
return res.data;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import {Input, Tag, Tooltip} from "antd";
|
||||
import {PresetColorType, PresetStatusColorType} from 'antd/lib/_util/colors'
|
||||
import {LiteralUnion} from 'antd/lib/_util/type'
|
||||
import React, {useRef, useState, useEffect} from "react";
|
||||
import { PlusOutlined } from '@ant-design/icons';
|
||||
|
||||
interface InputTagProp {
|
||||
value?: Array<string>,
|
||||
onChange?: (value: Array<string>) => void
|
||||
color?: LiteralUnion<PresetColorType | PresetStatusColorType, string>;
|
||||
addText?: string
|
||||
}
|
||||
export function InputTag(prop: InputTagProp) {
|
||||
const [ value, setValue ] = useState<Array<string>>(prop.value || []);
|
||||
const [ inputVisible, setInputVisible ] = useState(false);
|
||||
const [ inputValue, setInputValue ] = useState('');
|
||||
const [ editInputIndex, setEditInputIndex ] = useState(-1);
|
||||
const [ editInputValue, setEditInputValue ] = useState('');
|
||||
const inputRef = useRef(null as any);
|
||||
const editInputRef = useRef(null as any);
|
||||
useEffect(() => {
|
||||
if (inputVisible) {
|
||||
inputRef.current.focus()
|
||||
}
|
||||
}, [inputVisible]);
|
||||
useEffect(() => {
|
||||
if (editInputIndex !== -1) {
|
||||
editInputRef.current.focus();
|
||||
}
|
||||
}, [editInputIndex]);
|
||||
|
||||
const handleClose = (removedTag: string) => {
|
||||
const tags = value.filter(tag => tag !== removedTag);
|
||||
setValue(_ => tags);
|
||||
if (prop.onChange) {
|
||||
prop.onChange(tags);
|
||||
}
|
||||
}
|
||||
|
||||
const showInput = () => {
|
||||
setInputVisible(_ => true);
|
||||
}
|
||||
|
||||
const handleInputConfirm = () => {
|
||||
if (inputValue && value.indexOf(inputValue) === -1) {
|
||||
const newVal = [...value, inputValue];
|
||||
setValue(_ => newVal);
|
||||
if (prop.onChange) {
|
||||
prop.onChange(newVal);
|
||||
}
|
||||
}
|
||||
setInputVisible(_ => false);
|
||||
setInputValue(_ => '');
|
||||
}
|
||||
|
||||
const handleEditInputChange = (e: any) => {
|
||||
setEditInputValue(_ => e.target.value);
|
||||
}
|
||||
|
||||
const handleEditInputConfirm = () => {
|
||||
const newTags = value.slice();
|
||||
newTags[editInputIndex] = editInputValue;
|
||||
setValue(_ => newTags);
|
||||
if (prop.onChange) {
|
||||
prop.onChange(newTags);
|
||||
}
|
||||
setEditInputIndex(_ => -1);
|
||||
setEditInputValue(_ => '');
|
||||
}
|
||||
|
||||
const handleInputChange = (e: any) => {
|
||||
setInputValue(e.target.value);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{ value.map((tag, index) => {
|
||||
if (editInputIndex === index) {
|
||||
return (
|
||||
<Input ref={editInputRef} key={tag} size="small"
|
||||
value={editInputValue} onChange={handleEditInputChange}
|
||||
onBlur={handleEditInputConfirm} onPressEnter={handleInputConfirm} />
|
||||
);
|
||||
}
|
||||
const isLongTag = tag.length > 20;
|
||||
const tagElem = (
|
||||
<Tag color={prop.color || "default"} style={{userSelect: 'none'}} key={tag} closable onClose={() => handleClose(tag)}>
|
||||
<span onDoubleClick={e => {
|
||||
setEditInputIndex(_ => index);
|
||||
setEditInputValue(_ => tag);
|
||||
e.preventDefault();
|
||||
}}>
|
||||
{isLongTag ? `${tag.slice(0, 20)}...` : tag}
|
||||
</span>
|
||||
</Tag>
|
||||
);
|
||||
return isLongTag ? (
|
||||
<Tooltip title={tag} key={tag}>
|
||||
{tagElem}
|
||||
</Tooltip>
|
||||
) : ( tagElem );
|
||||
})}
|
||||
{inputVisible && (
|
||||
<Input ref={inputRef} type="text" size="small"
|
||||
style={{width: '78px', marginRight: '8px', verticalAlign: 'top'}} value={inputValue}
|
||||
onChange={handleInputChange} onBlur={handleInputConfirm}
|
||||
onPressEnter={handleInputConfirm} />
|
||||
)}
|
||||
{!inputVisible && (
|
||||
<Tag className="site-tag-plus" onClick={showInput} style={{background: '#fff', border: 'dashed'}}>
|
||||
<PlusOutlined/> {prop.addText || "Add Tag"}
|
||||
</Tag>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
}
|
||||
@@ -2,9 +2,11 @@ import React, { ReactElement, useContext, useEffect, useState } from "react";
|
||||
import { LoginContext, GlobalConfContext } from "../utils/context";
|
||||
import { Layout, Menu, Empty, Collapse, Card, Tag, Row, Col, Form, Tooltip, Button, Modal, Select,
|
||||
Input} from 'antd';
|
||||
import { SubscribeConfig, SubscribeResp, PlatformConfig } from '../utils/type';
|
||||
import { SubscribeConfig, SubscribeResp, PlatformConfig, CategoryConfig } from '../utils/type';
|
||||
import { SettingOutlined, BugOutlined, DeleteOutlined, CopyOutlined, PlusOutlined } from '@ant-design/icons';
|
||||
import { getSubscribe, getTargetName } from '../api/config';
|
||||
import { InputTag } from '../component/inputTag';
|
||||
import _ from "lodash";
|
||||
import './admin.css';
|
||||
|
||||
export function Admin() {
|
||||
@@ -63,7 +65,7 @@ function ConfigPage(prop: ConfigPageProp) {
|
||||
<Tooltip title="删除"><DeleteOutlined /></Tooltip>,
|
||||
<Tooltip title="添加到其他群"><CopyOutlined /></Tooltip>
|
||||
]}>
|
||||
<Form labelCol={{ span: 4 }}>
|
||||
<Form labelCol={{ span: 6 }}>
|
||||
<Form.Item label="订阅类型">
|
||||
{Object.keys(platformConf.categories).length > 0 ?
|
||||
config.cats.map((catKey: number) => (<Tag color="green" key={catKey}>{platformConf.categories[catKey]}</Tag>)) :
|
||||
@@ -103,6 +105,28 @@ function ConfigPage(prop: ConfigPageProp) {
|
||||
}
|
||||
}
|
||||
|
||||
interface InputTagCustomProp {
|
||||
value?: Array<string>,
|
||||
onChange?: (value: Array<string>) => void
|
||||
}
|
||||
function InputTagCustom(prop: InputTagCustomProp) {
|
||||
const [value, setValue] = useState(prop.value || []);
|
||||
const handleSetValue = (newVal: Array<string>) => {
|
||||
setValue(() => newVal);
|
||||
if (prop.onChange) {
|
||||
prop.onChange(newVal);
|
||||
}
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{value.length === 0 &&
|
||||
<Tag color="green">全部标签</Tag>
|
||||
}
|
||||
<InputTag color="blue" addText="添加标签" value={value} onChange={handleSetValue} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
interface AddModalProp {
|
||||
showModal: boolean,
|
||||
setShowModal: (s: boolean) => void
|
||||
@@ -111,26 +135,39 @@ function AddModal(prop: AddModalProp) {
|
||||
const [ confirmLoading, setConfirmLoading ] = useState<boolean>(false);
|
||||
const { platformConf } = useContext(GlobalConfContext);
|
||||
const [ hasTarget, setHasTarget ] = useState(false);
|
||||
const [ categories, setCategories ] = useState({} as CategoryConfig);
|
||||
const [ form ] = Form.useForm();
|
||||
const changePlatformSelect = (platform: string) => {
|
||||
setHasTarget(_ => platformConf[platform].hasTarget);
|
||||
setCategories(_ => platformConf[platform].categories);
|
||||
if (! platformConf[platform].hasTarget) {
|
||||
getTargetName(platform, 'default')
|
||||
.then(res => {
|
||||
console.log(res)
|
||||
form.setFieldsValue(() => { return {
|
||||
targetName: res.targetName
|
||||
}})
|
||||
form.setFieldsValue({
|
||||
targetName: res.targetName,
|
||||
target: ''
|
||||
})
|
||||
})
|
||||
} else {
|
||||
form.setFieldsValue({
|
||||
targetName: '',
|
||||
target: ''
|
||||
})
|
||||
}
|
||||
}
|
||||
const handleSubmit = (value: any) => {
|
||||
console.log(value);
|
||||
}
|
||||
const handleModleFinish = () => {
|
||||
form.submit();
|
||||
setConfirmLoading(() => true);
|
||||
}
|
||||
|
||||
return <Modal title="添加订阅" visible={prop.showModal}
|
||||
confirmLoading={confirmLoading} onCancel={() => prop.setShowModal(false)}>
|
||||
<Form form={form} labelCol={{ span: 6 }} name="b">
|
||||
confirmLoading={confirmLoading} onCancel={() => prop.setShowModal(false)}
|
||||
onOk={handleModleFinish}>
|
||||
<Form form={form} labelCol={{ span: 6 }} name="b" onFinish={handleSubmit}>
|
||||
<Form.Item label="平台" name="platformType" rules={[]}>
|
||||
<Select style={{ width: '80%' }} onChange={changePlatformSelect}>
|
||||
{Object.keys(platformConf).map(platformName =>
|
||||
@@ -138,11 +175,50 @@ function AddModal(prop: AddModalProp) {
|
||||
)}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item label="账号" name="target" rules={[]}>
|
||||
<Input placeholder={hasTarget ? "获取方式见文档" : "此平台不需要账号"} disabled={! hasTarget} style={{ width: "80%" }} />
|
||||
<Form.Item label="账号" name="target" rules={[
|
||||
{required: hasTarget, message: "请输入账号"},
|
||||
{validator: async (_, value) => {
|
||||
try {
|
||||
const res = await getTargetName(form.getFieldValue('platformType'), value);
|
||||
if (res.targetName) {
|
||||
form.setFieldsValue({
|
||||
targetName: res.targetName
|
||||
})
|
||||
return Promise.resolve()
|
||||
} else {
|
||||
form.setFieldsValue({
|
||||
targetName: ''
|
||||
})
|
||||
return Promise.reject("账号不正确,请重新检查账号")
|
||||
}
|
||||
} catch {
|
||||
return Promise.reject('服务器错误,请稍后再试')
|
||||
}
|
||||
}
|
||||
}
|
||||
]}>
|
||||
<Input placeholder={hasTarget ? "获取方式见文档" : "此平台不需要账号"}
|
||||
disabled={! hasTarget} style={{ width: "80%" }}/>
|
||||
</Form.Item>
|
||||
<Form.Item label="账号名称" name="targetName">
|
||||
<Input style={{ width: "80%" }} />
|
||||
<Input style={{ width: "80%" }} disabled />
|
||||
</Form.Item>
|
||||
<Form.Item label="订阅分类" name="categories">
|
||||
<Select style={{ width: '80%' }} mode="multiple"
|
||||
disabled={Object.keys(categories).length === 0}
|
||||
placeholder={Object.keys(categories).length > 0 ?
|
||||
"请选择要订阅的分类" : "本平台不支持分类"}>
|
||||
{Object.keys(categories).length > 0 &&
|
||||
Object.keys(categories).map((indexStr) =>
|
||||
<Select.Option key={indexStr} value={parseInt(indexStr)}>
|
||||
{categories[parseInt(indexStr)]}
|
||||
</Select.Option>
|
||||
)
|
||||
}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item label="订阅Tag" name="tags">
|
||||
<InputTagCustom/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
@@ -34,7 +34,7 @@ export interface AllPlatformConf {
|
||||
[idx: string]: PlatformConfig;
|
||||
}
|
||||
|
||||
interface CategoryConfig {
|
||||
export interface CategoryConfig {
|
||||
[idx: number]: string
|
||||
}
|
||||
|
||||
@@ -66,3 +66,11 @@ export interface SubscribeResp {
|
||||
export interface TargetNameResp {
|
||||
targetName: string
|
||||
}
|
||||
|
||||
export interface CreateSubscribeReq {
|
||||
platformName: string,
|
||||
targetName: string,
|
||||
target: string,
|
||||
categories: Array<string>,
|
||||
tags: Array<string>
|
||||
}
|
||||
|
||||
@@ -1899,6 +1899,11 @@
|
||||
resolved "https://registry.npm.taobao.org/@types/json5/download/@types/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
||||
integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4=
|
||||
|
||||
"@types/lodash@^4.14.175":
|
||||
version "4.14.175"
|
||||
resolved "https://registry.npmmirror.com/@types/lodash/download/@types/lodash-4.14.175.tgz#b78dfa959192b01fae0ad90e166478769b215f45"
|
||||
integrity sha1-t436lZGSsB+uCtkOFmR4dpshX0U=
|
||||
|
||||
"@types/minimatch@*":
|
||||
version "3.0.5"
|
||||
resolved "https://registry.nlark.com/@types/minimatch/download/@types/minimatch-3.0.5.tgz?cache=0&sync_timestamp=1629708375365&other_urls=https%3A%2F%2Fregistry.nlark.com%2F%40types%2Fminimatch%2Fdownload%2F%40types%2Fminimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40"
|
||||
|
||||
Reference in New Issue
Block a user