This commit is contained in:
felinae98
2021-10-01 16:17:22 +08:00
parent cdeea09522
commit 5d9872af29
11 changed files with 339 additions and 19 deletions
+3 -1
View File
@@ -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"
}
}
+2 -2
View File
@@ -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 />
+6 -1
View File
@@ -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;
}
+117
View File
@@ -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>
)}
</>
);
}
+86 -10
View File
@@ -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>
+9 -1
View File
@@ -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>
}
+5
View File
@@ -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"