mirror of
https://github.com/suyiiyii/nonebot-bison.git
synced 2025-06-05 11:26:43 +08:00
format frontend code
This commit is contained in:
parent
9055a039a8
commit
649c1cf8f2
@ -20,4 +20,4 @@ repos:
|
|||||||
rev: v2.5.1
|
rev: v2.5.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: prettier
|
- id: prettier
|
||||||
types_or: [markdown]
|
types_or: [markdown, ts, tsx]
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from "@testing-library/react";
|
||||||
import App from './App';
|
import App from "./App";
|
||||||
|
|
||||||
test('renders learn react link', () => {
|
test("renders learn react link", () => {
|
||||||
render(<App />);
|
render(<App />);
|
||||||
const linkElement = screen.getByText(/learn react/i);
|
const linkElement = screen.getByText(/learn react/i);
|
||||||
expect(linkElement).toBeInTheDocument();
|
expect(linkElement).toBeInTheDocument();
|
||||||
|
@ -1,49 +1,46 @@
|
|||||||
import 'antd/dist/antd.css';
|
import "antd/dist/antd.css";
|
||||||
import React, {useEffect} from 'react';
|
import React, { useEffect } from "react";
|
||||||
import {useDispatch, useSelector} from 'react-redux';
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import {BrowserRouter as Router, Route, Switch} from 'react-router-dom';
|
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
|
||||||
import './App.css';
|
import "./App.css";
|
||||||
import {Admin} from './pages/admin';
|
import { Admin } from "./pages/admin";
|
||||||
import {Auth} from './pages/auth';
|
import { Auth } from "./pages/auth";
|
||||||
import {getGlobalConf} from './store/globalConfSlice';
|
import { getGlobalConf } from "./store/globalConfSlice";
|
||||||
import {useAppSelector} from './store/hooks';
|
import { useAppSelector } from "./store/hooks";
|
||||||
import {loadLoginState, loginSelector} from './store/loginSlice';
|
import { loadLoginState, loginSelector } from "./store/loginSlice";
|
||||||
|
|
||||||
|
|
||||||
function LoginSwitch() {
|
function LoginSwitch() {
|
||||||
const login = useSelector(loginSelector)
|
const login = useSelector(loginSelector);
|
||||||
if (login.login) {
|
if (login.login) {
|
||||||
return <Admin />;
|
return <Admin />;
|
||||||
} else {
|
} else {
|
||||||
return (
|
return <div>not login</div>;
|
||||||
<div>
|
|
||||||
not login
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch();
|
||||||
const globalConf = useAppSelector(state => state.globalConf)
|
const globalConf = useAppSelector((state) => state.globalConf);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(getGlobalConf());
|
dispatch(getGlobalConf());
|
||||||
dispatch(loadLoginState())
|
dispatch(loadLoginState());
|
||||||
}, [dispatch]);
|
}, [dispatch]);
|
||||||
return <>
|
return (
|
||||||
{ globalConf.loaded &&
|
<>
|
||||||
|
{globalConf.loaded && (
|
||||||
<Router basename="/bison">
|
<Router basename="/bison">
|
||||||
<Switch>
|
<Switch>
|
||||||
<Route path="/auth/:code">
|
<Route path="/auth/:code">
|
||||||
<Auth />
|
<Auth />
|
||||||
</Route>
|
</Route>
|
||||||
<Route path="/admin/">
|
<Route path="/admin/">
|
||||||
<LoginSwitch />
|
<LoginSwitch />
|
||||||
</Route>
|
</Route>
|
||||||
</Switch>
|
</Switch>
|
||||||
</Router>
|
</Router>
|
||||||
}
|
)}
|
||||||
</>;
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default App;
|
export default App;
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { GlobalConf, TokenResp, SubscribeResp, TargetNameResp, SubscribeConfig } from "../utils/type";
|
import {
|
||||||
import { baseUrl } from './utils';
|
GlobalConf,
|
||||||
|
TokenResp,
|
||||||
|
SubscribeResp,
|
||||||
|
TargetNameResp,
|
||||||
|
SubscribeConfig,
|
||||||
|
} from "../utils/type";
|
||||||
|
import { baseUrl } from "./utils";
|
||||||
|
|
||||||
export async function getGlobalConf(): Promise<GlobalConf> {
|
export async function getGlobalConf(): Promise<GlobalConf> {
|
||||||
const res = await axios.get<GlobalConf>(`${baseUrl}global_conf`);
|
const res = await axios.get<GlobalConf>(`${baseUrl}global_conf`);
|
||||||
@ -8,7 +14,9 @@ export async function getGlobalConf(): Promise<GlobalConf> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function auth(token: string): Promise<TokenResp> {
|
export async function auth(token: string): Promise<TokenResp> {
|
||||||
const res = await axios.get<TokenResp>(`${baseUrl}auth`, {params: {token}});
|
const res = await axios.get<TokenResp>(`${baseUrl}auth`, {
|
||||||
|
params: { token },
|
||||||
|
});
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17,22 +25,39 @@ export async function getSubscribe(): Promise<SubscribeResp> {
|
|||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getTargetName(platformName: string, target: string): Promise<TargetNameResp> {
|
export async function getTargetName(
|
||||||
const res = await axios.get(`${baseUrl}target_name`, {params: {platformName, target}});
|
platformName: string,
|
||||||
|
target: string
|
||||||
|
): Promise<TargetNameResp> {
|
||||||
|
const res = await axios.get(`${baseUrl}target_name`, {
|
||||||
|
params: { platformName, target },
|
||||||
|
});
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function addSubscribe(groupNumber: string, req: SubscribeConfig) {
|
export async function addSubscribe(groupNumber: string, req: SubscribeConfig) {
|
||||||
const res = await axios.post(`${baseUrl}subs`, req, {params: {groupNumber}})
|
const res = await axios.post(`${baseUrl}subs`, req, {
|
||||||
|
params: { groupNumber },
|
||||||
|
});
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function delSubscribe(groupNumber: string, platformName: string, target: string) {
|
export async function delSubscribe(
|
||||||
const res = await axios.delete(`${baseUrl}subs`, {params: {groupNumber, platformName, target}});
|
groupNumber: string,
|
||||||
|
platformName: string,
|
||||||
|
target: string
|
||||||
|
) {
|
||||||
|
const res = await axios.delete(`${baseUrl}subs`, {
|
||||||
|
params: { groupNumber, platformName, target },
|
||||||
|
});
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function updateSubscribe(groupNumber: string, req: SubscribeConfig) {
|
export async function updateSubscribe(
|
||||||
return axios.patch(`${baseUrl}subs`, req, {params: {groupNumber}})
|
groupNumber: string,
|
||||||
.then(res => res.data);
|
req: SubscribeConfig
|
||||||
|
) {
|
||||||
|
return axios
|
||||||
|
.patch(`${baseUrl}subs`, req, { params: { groupNumber } })
|
||||||
|
.then((res) => res.data);
|
||||||
}
|
}
|
||||||
|
@ -1,51 +1,61 @@
|
|||||||
import axios, {AxiosError} from "axios";
|
import axios, { AxiosError } from "axios";
|
||||||
import {Store} from "src/store";
|
import { Store } from "src/store";
|
||||||
import { clearLoginStatus } from 'src/store/loginSlice';
|
import { clearLoginStatus } from "src/store/loginSlice";
|
||||||
// import { useContext } from 'react';
|
// import { useContext } from 'react';
|
||||||
// import { LoginContext } from "../utils/context";
|
// import { LoginContext } from "../utils/context";
|
||||||
|
|
||||||
export const baseUrl = '/bison/api/'
|
export const baseUrl = "/bison/api/";
|
||||||
let store: Store
|
let store: Store;
|
||||||
export const injectStore = (_store: Store) => {
|
export const injectStore = (_store: Store) => {
|
||||||
store = _store
|
store = _store;
|
||||||
}
|
};
|
||||||
|
|
||||||
// const loginStatus = useContext(LoginContext);
|
// const loginStatus = useContext(LoginContext);
|
||||||
axios.interceptors.request.use(function (config) {
|
axios.interceptors.request.use(
|
||||||
if (config.url && config.url.startsWith(baseUrl) && config.url !== `${baseUrl}auth`
|
function (config) {
|
||||||
&& config.url !== `${baseUrl}global_conf`) {
|
if (
|
||||||
const token = localStorage.getItem('token');
|
config.url &&
|
||||||
if (token) {
|
config.url.startsWith(baseUrl) &&
|
||||||
config.headers['Authorization'] = `Bearer ${token}`;
|
config.url !== `${baseUrl}auth` &&
|
||||||
} else {
|
config.url !== `${baseUrl}global_conf`
|
||||||
throw new axios.Cancel('User not login');
|
) {
|
||||||
|
const token = localStorage.getItem("token");
|
||||||
|
if (token) {
|
||||||
|
config.headers["Authorization"] = `Bearer ${token}`;
|
||||||
|
} else {
|
||||||
|
throw new axios.Cancel("User not login");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
function (error) {
|
||||||
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
return config;
|
);
|
||||||
}, function (error) {
|
|
||||||
return Promise.reject(error);
|
|
||||||
});
|
|
||||||
|
|
||||||
axios.interceptors.response.use(function (response) {
|
axios.interceptors.response.use(
|
||||||
// const data = response.data;
|
function (response) {
|
||||||
// const parseToMap = (item: any): any => {
|
// const data = response.data;
|
||||||
// if (item instanceof Array) {
|
// const parseToMap = (item: any): any => {
|
||||||
// return item.map(parseToMap);
|
// if (item instanceof Array) {
|
||||||
// } else if (item instanceof Object) {
|
// return item.map(parseToMap);
|
||||||
// let res = new Map();
|
// } else if (item instanceof Object) {
|
||||||
// for (const key of Object.keys(item)) {
|
// let res = new Map();
|
||||||
// res.set(key, parseToMap(item[key]));
|
// for (const key of Object.keys(item)) {
|
||||||
// }
|
// res.set(key, parseToMap(item[key]));
|
||||||
// return res;
|
// }
|
||||||
// } else {
|
// return res;
|
||||||
// return item;
|
// } else {
|
||||||
// }
|
// return item;
|
||||||
// }
|
// }
|
||||||
// response.data = parseToMap(data);
|
// }
|
||||||
return response;
|
// response.data = parseToMap(data);
|
||||||
}, function(error: AxiosError) {
|
return response;
|
||||||
if(error.response && error.response.status === 401) {
|
},
|
||||||
store.dispatch(clearLoginStatus());
|
function (error: AxiosError) {
|
||||||
|
if (error.response && error.response.status === 401) {
|
||||||
|
store.dispatch(clearLoginStatus());
|
||||||
|
}
|
||||||
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
return Promise.reject(error);
|
);
|
||||||
});
|
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import {Form, Input, Modal, Select, Tag} from 'antd';
|
import { Form, Input, Modal, Select, Tag } from "antd";
|
||||||
import React, {useEffect, useState} from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import {useSelector} from 'react-redux';
|
import { useSelector } from "react-redux";
|
||||||
import {addSubscribe, getTargetName, updateSubscribe} from 'src/api/config';
|
import { addSubscribe, getTargetName, updateSubscribe } from "src/api/config";
|
||||||
import {InputTag} from 'src/component/inputTag';
|
import { InputTag } from "src/component/inputTag";
|
||||||
import {platformConfSelector} from 'src/store/globalConfSlice';
|
import { platformConfSelector } from "src/store/globalConfSlice";
|
||||||
import {CategoryConfig, SubscribeConfig} from 'src/utils/type';
|
import { CategoryConfig, SubscribeConfig } from "src/utils/type";
|
||||||
|
|
||||||
interface InputTagCustomProp {
|
interface InputTagCustomProp {
|
||||||
value?: Array<string>,
|
value?: Array<string>;
|
||||||
onChange?: (value: Array<string>) => void,
|
onChange?: (value: Array<string>) => void;
|
||||||
disabled?: boolean
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
function InputTagCustom(prop: InputTagCustomProp) {
|
function InputTagCustom(prop: InputTagCustomProp) {
|
||||||
const [value, setValue] = useState(prop.value || []);
|
const [value, setValue] = useState(prop.value || []);
|
||||||
@ -18,165 +18,202 @@ function InputTagCustom(prop: InputTagCustomProp) {
|
|||||||
if (prop.onChange) {
|
if (prop.onChange) {
|
||||||
prop.onChange(newVal);
|
prop.onChange(newVal);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (prop.value) {
|
if (prop.value) {
|
||||||
setValue(prop.value);
|
setValue(prop.value);
|
||||||
}
|
}
|
||||||
}, [prop.value])
|
}, [prop.value]);
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{
|
{prop.disabled ? (
|
||||||
prop.disabled ? <Tag color="default">不支持标签</Tag>:
|
<Tag color="default">不支持标签</Tag>
|
||||||
|
) : (
|
||||||
<>
|
<>
|
||||||
{value.length === 0 &&
|
{value.length === 0 && <Tag color="green">全部标签</Tag>}
|
||||||
<Tag color="green">全部标签</Tag>
|
<InputTag
|
||||||
}
|
color="blue"
|
||||||
<InputTag color="blue" addText="添加标签" value={value} onChange={handleSetValue} />
|
addText="添加标签"
|
||||||
</>
|
value={value}
|
||||||
}
|
onChange={handleSetValue}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AddModalProp {
|
interface AddModalProp {
|
||||||
showModal: boolean,
|
showModal: boolean;
|
||||||
groupNumber: string,
|
groupNumber: string;
|
||||||
setShowModal: (s: boolean) => void,
|
setShowModal: (s: boolean) => void;
|
||||||
refresh: () => void
|
refresh: () => void;
|
||||||
initVal?: SubscribeConfig
|
initVal?: SubscribeConfig;
|
||||||
}
|
}
|
||||||
export function AddModal({
|
export function AddModal({
|
||||||
showModal, groupNumber, setShowModal, refresh, initVal
|
showModal,
|
||||||
|
groupNumber,
|
||||||
|
setShowModal,
|
||||||
|
refresh,
|
||||||
|
initVal,
|
||||||
}: AddModalProp) {
|
}: AddModalProp) {
|
||||||
const [ confirmLoading, setConfirmLoading ] = useState<boolean>(false);
|
const [confirmLoading, setConfirmLoading] = useState<boolean>(false);
|
||||||
const platformConf = useSelector(platformConfSelector)
|
const platformConf = useSelector(platformConfSelector);
|
||||||
const [ hasTarget, setHasTarget ] = useState(false);
|
const [hasTarget, setHasTarget] = useState(false);
|
||||||
const [ categories, setCategories ] = useState({} as CategoryConfig);
|
const [categories, setCategories] = useState({} as CategoryConfig);
|
||||||
const [ enabledTag, setEnableTag ] = useState(false);
|
const [enabledTag, setEnableTag] = useState(false);
|
||||||
const [ form ] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [ inited, setInited ] = useState(false);
|
const [inited, setInited] = useState(false);
|
||||||
const changePlatformSelect = (platform: string) => {
|
const changePlatformSelect = (platform: string) => {
|
||||||
setHasTarget(_ => platformConf[platform].hasTarget);
|
setHasTarget((_) => platformConf[platform].hasTarget);
|
||||||
setCategories(_ => platformConf[platform].categories);
|
setCategories((_) => platformConf[platform].categories);
|
||||||
setEnableTag(platformConf[platform].enabledTag)
|
setEnableTag(platformConf[platform].enabledTag);
|
||||||
if (! platformConf[platform].hasTarget) {
|
if (!platformConf[platform].hasTarget) {
|
||||||
getTargetName(platform, 'default')
|
getTargetName(platform, "default").then((res) => {
|
||||||
.then(res => {
|
console.log(res);
|
||||||
console.log(res)
|
form.setFieldsValue({
|
||||||
form.setFieldsValue({
|
targetName: res.targetName,
|
||||||
targetName: res.targetName,
|
target: "",
|
||||||
target: ''
|
});
|
||||||
})
|
});
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
targetName: '',
|
targetName: "",
|
||||||
target: ''
|
target: "",
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
const handleSubmit = (value: any) => {
|
const handleSubmit = (value: any) => {
|
||||||
let newVal = Object.assign({}, value)
|
let newVal = Object.assign({}, value);
|
||||||
if (typeof newVal.tags !== 'object') {
|
if (typeof newVal.tags !== "object") {
|
||||||
newVal.tags = []
|
newVal.tags = [];
|
||||||
}
|
}
|
||||||
if (typeof newVal.cats !== 'object') {
|
if (typeof newVal.cats !== "object") {
|
||||||
newVal.cats = []
|
newVal.cats = [];
|
||||||
}
|
}
|
||||||
if (newVal.target === '') {
|
if (newVal.target === "") {
|
||||||
newVal.target = 'default'
|
newVal.target = "default";
|
||||||
}
|
}
|
||||||
if (initVal) { // patch
|
if (initVal) {
|
||||||
updateSubscribe(groupNumber, newVal)
|
// patch
|
||||||
.then(() => {
|
updateSubscribe(groupNumber, newVal).then(() => {
|
||||||
setConfirmLoading(false);
|
|
||||||
setShowModal(false);
|
|
||||||
form.resetFields();
|
|
||||||
refresh();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
addSubscribe(groupNumber, newVal)
|
|
||||||
.then(() => {
|
|
||||||
setConfirmLoading(false);
|
setConfirmLoading(false);
|
||||||
setShowModal(false);
|
setShowModal(false);
|
||||||
form.resetFields();
|
form.resetFields();
|
||||||
refresh();
|
refresh();
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
addSubscribe(groupNumber, newVal).then(() => {
|
||||||
|
setConfirmLoading(false);
|
||||||
|
setShowModal(false);
|
||||||
|
form.resetFields();
|
||||||
|
refresh();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
const handleModleFinish = () => {
|
const handleModleFinish = () => {
|
||||||
form.submit();
|
form.submit();
|
||||||
setConfirmLoading(() => true);
|
setConfirmLoading(() => true);
|
||||||
}
|
};
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (initVal && !inited) {
|
if (initVal && !inited) {
|
||||||
const platformName = initVal.platformName;
|
const platformName = initVal.platformName;
|
||||||
setHasTarget(platformConf[platformName].hasTarget);
|
setHasTarget(platformConf[platformName].hasTarget);
|
||||||
setCategories(platformConf[platformName].categories);
|
setCategories(platformConf[platformName].categories);
|
||||||
setEnableTag(platformConf[platformName].enabledTag);
|
setEnableTag(platformConf[platformName].enabledTag);
|
||||||
setInited(true)
|
setInited(true);
|
||||||
form.setFieldsValue(initVal)
|
form.setFieldsValue(initVal);
|
||||||
}
|
}
|
||||||
}, [initVal, form, platformConf, inited])
|
}, [initVal, form, platformConf, inited]);
|
||||||
return <Modal title="添加订阅" visible={showModal}
|
return (
|
||||||
confirmLoading={confirmLoading} onCancel={() => setShowModal(false)}
|
<Modal
|
||||||
onOk={handleModleFinish}>
|
title="添加订阅"
|
||||||
<Form form={form} labelCol={{ span: 6 }} name="b" onFinish={handleSubmit} >
|
visible={showModal}
|
||||||
<Form.Item label="平台" name="platformName" rules={[]}>
|
confirmLoading={confirmLoading}
|
||||||
<Select style={{ width: '80%' }} onChange={changePlatformSelect}>
|
onCancel={() => setShowModal(false)}
|
||||||
{Object.keys(platformConf).map(platformName =>
|
onOk={handleModleFinish}
|
||||||
<Select.Option key={platformName} value={platformName}>{platformConf[platformName].name}</Select.Option>
|
>
|
||||||
)}
|
<Form form={form} labelCol={{ span: 6 }} name="b" onFinish={handleSubmit}>
|
||||||
</Select>
|
<Form.Item label="平台" name="platformName" rules={[]}>
|
||||||
</Form.Item>
|
<Select style={{ width: "80%" }} onChange={changePlatformSelect}>
|
||||||
<Form.Item label="账号" name="target" rules={[
|
{Object.keys(platformConf).map((platformName) => (
|
||||||
{required: hasTarget, message: "请输入账号"},
|
<Select.Option key={platformName} value={platformName}>
|
||||||
{validator: async (_, value) => {
|
{platformConf[platformName].name}
|
||||||
try {
|
|
||||||
const res = await getTargetName(form.getFieldValue('platformName'), 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%" }} disabled />
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item label="订阅分类" name="cats" rules={[
|
|
||||||
{required: Object.keys(categories).length > 0, message: "请至少选择一个分类进行订阅"}
|
|
||||||
]}>
|
|
||||||
<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.Option>
|
||||||
)
|
))}
|
||||||
}
|
</Select>
|
||||||
</Select>
|
</Form.Item>
|
||||||
</Form.Item>
|
<Form.Item
|
||||||
<Form.Item label="订阅Tag" name="tags">
|
label="账号"
|
||||||
<InputTagCustom disabled={!enabledTag}/>
|
name="target"
|
||||||
</Form.Item>
|
rules={[
|
||||||
</Form>
|
{ required: hasTarget, message: "请输入账号" },
|
||||||
|
{
|
||||||
|
validator: async (_, value) => {
|
||||||
|
try {
|
||||||
|
const res = await getTargetName(
|
||||||
|
form.getFieldValue("platformName"),
|
||||||
|
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%" }} disabled />
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
label="订阅分类"
|
||||||
|
name="cats"
|
||||||
|
rules={[
|
||||||
|
{
|
||||||
|
required: Object.keys(categories).length > 0,
|
||||||
|
message: "请至少选择一个分类进行订阅",
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<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 disabled={!enabledTag} />
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,122 +1,151 @@
|
|||||||
import {Input, Tag, Tooltip} from "antd";
|
import { Input, Tag, Tooltip } from "antd";
|
||||||
import {PresetColorType, PresetStatusColorType} from 'antd/lib/_util/colors'
|
import { PresetColorType, PresetStatusColorType } from "antd/lib/_util/colors";
|
||||||
import {LiteralUnion} from 'antd/lib/_util/type'
|
import { LiteralUnion } from "antd/lib/_util/type";
|
||||||
import React, {useRef, useState, useEffect} from "react";
|
import React, { useRef, useState, useEffect } from "react";
|
||||||
import { PlusOutlined } from '@ant-design/icons';
|
import { PlusOutlined } from "@ant-design/icons";
|
||||||
|
|
||||||
interface InputTagProp {
|
interface InputTagProp {
|
||||||
value?: Array<string>,
|
value?: Array<string>;
|
||||||
onChange?: (value: Array<string>) => void
|
onChange?: (value: Array<string>) => void;
|
||||||
color?: LiteralUnion<PresetColorType | PresetStatusColorType, string>;
|
color?: LiteralUnion<PresetColorType | PresetStatusColorType, string>;
|
||||||
addText?: string
|
addText?: string;
|
||||||
}
|
}
|
||||||
export function InputTag(prop: InputTagProp) {
|
export function InputTag(prop: InputTagProp) {
|
||||||
const [ value, setValue ] = useState<Array<string>>(prop.value || []);
|
const [value, setValue] = useState<Array<string>>(prop.value || []);
|
||||||
const [ inputVisible, setInputVisible ] = useState(false);
|
const [inputVisible, setInputVisible] = useState(false);
|
||||||
const [ inputValue, setInputValue ] = useState('');
|
const [inputValue, setInputValue] = useState("");
|
||||||
const [ editInputIndex, setEditInputIndex ] = useState(-1);
|
const [editInputIndex, setEditInputIndex] = useState(-1);
|
||||||
const [ editInputValue, setEditInputValue ] = useState('');
|
const [editInputValue, setEditInputValue] = useState("");
|
||||||
const inputRef = useRef(null as any);
|
const inputRef = useRef(null as any);
|
||||||
const editInputRef = useRef(null as any);
|
const editInputRef = useRef(null as any);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (prop.value) {
|
if (prop.value) {
|
||||||
setValue(prop.value);
|
setValue(prop.value);
|
||||||
}
|
}
|
||||||
}, [prop.value])
|
}, [prop.value]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (inputVisible) {
|
if (inputVisible) {
|
||||||
inputRef.current.focus()
|
inputRef.current.focus();
|
||||||
}
|
}
|
||||||
}, [inputVisible]);
|
}, [inputVisible]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (editInputIndex !== -1) {
|
if (editInputIndex !== -1) {
|
||||||
editInputRef.current.focus();
|
editInputRef.current.focus();
|
||||||
}
|
}
|
||||||
}, [editInputIndex]);
|
}, [editInputIndex]);
|
||||||
|
|
||||||
const handleClose = (removedTag: string) => {
|
const handleClose = (removedTag: string) => {
|
||||||
const tags = value.filter(tag => tag !== removedTag);
|
const tags = value.filter((tag) => tag !== removedTag);
|
||||||
setValue(_ => tags);
|
setValue((_) => tags);
|
||||||
if (prop.onChange) {
|
if (prop.onChange) {
|
||||||
prop.onChange(tags);
|
prop.onChange(tags);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
const showInput = () => {
|
const showInput = () => {
|
||||||
setInputVisible(_ => true);
|
setInputVisible((_) => true);
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleInputConfirm = () => {
|
const handleInputConfirm = () => {
|
||||||
if (inputValue && value.indexOf(inputValue) === -1) {
|
if (inputValue && value.indexOf(inputValue) === -1) {
|
||||||
const newVal = [...value, inputValue];
|
const newVal = [...value, inputValue];
|
||||||
setValue(_ => newVal);
|
setValue((_) => newVal);
|
||||||
if (prop.onChange) {
|
if (prop.onChange) {
|
||||||
prop.onChange(newVal);
|
prop.onChange(newVal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setInputVisible(_ => false);
|
setInputVisible((_) => false);
|
||||||
setInputValue(_ => '');
|
setInputValue((_) => "");
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleEditInputChange = (e: any) => {
|
const handleEditInputChange = (e: any) => {
|
||||||
setEditInputValue(_ => e.target.value);
|
setEditInputValue((_) => e.target.value);
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleEditInputConfirm = () => {
|
const handleEditInputConfirm = () => {
|
||||||
const newTags = value.slice();
|
const newTags = value.slice();
|
||||||
newTags[editInputIndex] = editInputValue;
|
newTags[editInputIndex] = editInputValue;
|
||||||
setValue(_ => newTags);
|
setValue((_) => newTags);
|
||||||
if (prop.onChange) {
|
if (prop.onChange) {
|
||||||
prop.onChange(newTags);
|
prop.onChange(newTags);
|
||||||
}
|
}
|
||||||
setEditInputIndex(_ => -1);
|
setEditInputIndex((_) => -1);
|
||||||
setEditInputValue(_ => '');
|
setEditInputValue((_) => "");
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleInputChange = (e: any) => {
|
const handleInputChange = (e: any) => {
|
||||||
setInputValue(e.target.value);
|
setInputValue(e.target.value);
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{ value.map((tag, index) => {
|
{value.map((tag, index) => {
|
||||||
if (editInputIndex === index) {
|
if (editInputIndex === index) {
|
||||||
return (
|
return (
|
||||||
<Input ref={editInputRef} key={tag} size="small"
|
<Input
|
||||||
value={editInputValue} onChange={handleEditInputChange}
|
ref={editInputRef}
|
||||||
onBlur={handleEditInputConfirm} onPressEnter={handleInputConfirm} />
|
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 ? (
|
||||||
const isLongTag = tag.length > 20;
|
<Tooltip title={tag} key={tag}>
|
||||||
const tagElem = (
|
{tagElem}
|
||||||
<Tag color={prop.color || "default"} style={{userSelect: 'none'}} key={tag} closable onClose={() => handleClose(tag)}>
|
</Tooltip>
|
||||||
<span onDoubleClick={e => {
|
) : (
|
||||||
setEditInputIndex(_ => index);
|
tagElem
|
||||||
setEditInputValue(_ => tag);
|
|
||||||
e.preventDefault();
|
|
||||||
}}>
|
|
||||||
{isLongTag ? `${tag.slice(0, 20)}...` : tag}
|
|
||||||
</span>
|
|
||||||
</Tag>
|
|
||||||
);
|
);
|
||||||
return isLongTag ? (
|
|
||||||
<Tooltip title={tag} key={tag}>
|
|
||||||
{tagElem}
|
|
||||||
</Tooltip>
|
|
||||||
) : ( tagElem );
|
|
||||||
})}
|
})}
|
||||||
{inputVisible && (
|
{inputVisible && (
|
||||||
<Input ref={inputRef} type="text" size="small"
|
<Input
|
||||||
style={{width: '78px', marginRight: '8px', verticalAlign: 'top'}} value={inputValue}
|
ref={inputRef}
|
||||||
onChange={handleInputChange} onBlur={handleInputConfirm}
|
type="text"
|
||||||
onPressEnter={handleInputConfirm} />
|
size="small"
|
||||||
|
style={{ width: "78px", marginRight: "8px", verticalAlign: "top" }}
|
||||||
|
value={inputValue}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
onBlur={handleInputConfirm}
|
||||||
|
onPressEnter={handleInputConfirm}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{!inputVisible && (
|
{!inputVisible && (
|
||||||
<Tag className="site-tag-plus" onClick={showInput} style={{background: '#fff', border: 'dashed thin', borderColor: '#bfbfbf' }}>
|
<Tag
|
||||||
<PlusOutlined/> {prop.addText || "Add Tag"}
|
className="site-tag-plus"
|
||||||
|
onClick={showInput}
|
||||||
|
style={{
|
||||||
|
background: "#fff",
|
||||||
|
border: "dashed thin",
|
||||||
|
borderColor: "#bfbfbf",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<PlusOutlined /> {prop.addText || "Add Tag"}
|
||||||
</Tag>
|
</Tag>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,108 +1,190 @@
|
|||||||
import {CopyOutlined, DeleteOutlined, EditOutlined} from '@ant-design/icons';
|
import { CopyOutlined, DeleteOutlined, EditOutlined } from "@ant-design/icons";
|
||||||
import {Card, Col, Form, message, Popconfirm, Select, Tag, Tooltip} from 'antd';
|
import {
|
||||||
import Modal from 'antd/lib/modal/Modal';
|
Card,
|
||||||
import React, {useState} from "react";
|
Col,
|
||||||
import {useDispatch, useSelector} from 'react-redux';
|
Form,
|
||||||
import {addSubscribe, delSubscribe} from 'src/api/config';
|
message,
|
||||||
import {platformConfSelector} from 'src/store/globalConfSlice';
|
Popconfirm,
|
||||||
import {groupConfigSelector, updateGroupSubs} from 'src/store/groupConfigSlice';
|
Select,
|
||||||
import {PlatformConfig, SubscribeConfig, SubscribeResp} from 'src/utils/type';
|
Tag,
|
||||||
import {AddModal} from './addSubsModal';
|
Tooltip,
|
||||||
|
} from "antd";
|
||||||
|
import Modal from "antd/lib/modal/Modal";
|
||||||
|
import React, { useState } from "react";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { addSubscribe, delSubscribe } from "src/api/config";
|
||||||
|
import { platformConfSelector } from "src/store/globalConfSlice";
|
||||||
|
import {
|
||||||
|
groupConfigSelector,
|
||||||
|
updateGroupSubs,
|
||||||
|
} from "src/store/groupConfigSlice";
|
||||||
|
import { PlatformConfig, SubscribeConfig, SubscribeResp } from "src/utils/type";
|
||||||
|
import { AddModal } from "./addSubsModal";
|
||||||
|
|
||||||
interface CopyModalProp {
|
interface CopyModalProp {
|
||||||
setShowModal: (modalShow: boolean) => void
|
setShowModal: (modalShow: boolean) => void;
|
||||||
showModal: boolean
|
showModal: boolean;
|
||||||
config: SubscribeConfig,
|
config: SubscribeConfig;
|
||||||
groups: SubscribeResp
|
groups: SubscribeResp;
|
||||||
currentGroupNumber: string
|
currentGroupNumber: string;
|
||||||
reload: () => void
|
reload: () => void;
|
||||||
}
|
}
|
||||||
function CopyModal({setShowModal,config,
|
function CopyModal({
|
||||||
currentGroupNumber,groups,showModal,reload}: CopyModalProp) {
|
setShowModal,
|
||||||
const [confirmLoading, setConfirmLoading] = useState(false)
|
config,
|
||||||
const [ selectedGroups, setSelectGroups ] = useState<Array<string>>([]);
|
currentGroupNumber,
|
||||||
const postReqs = async (selectedGroups: Array<string>, config: SubscribeConfig) => {
|
groups,
|
||||||
for(let selectedGroup of selectedGroups) {
|
showModal,
|
||||||
|
reload,
|
||||||
|
}: CopyModalProp) {
|
||||||
|
const [confirmLoading, setConfirmLoading] = useState(false);
|
||||||
|
const [selectedGroups, setSelectGroups] = useState<Array<string>>([]);
|
||||||
|
const postReqs = async (
|
||||||
|
selectedGroups: Array<string>,
|
||||||
|
config: SubscribeConfig
|
||||||
|
) => {
|
||||||
|
for (let selectedGroup of selectedGroups) {
|
||||||
await addSubscribe(selectedGroup, config);
|
await addSubscribe(selectedGroup, config);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
const handleOk = () => {
|
const handleOk = () => {
|
||||||
if (selectedGroups.length === 0) {
|
if (selectedGroups.length === 0) {
|
||||||
message.error("请至少选择一个目标群");
|
message.error("请至少选择一个目标群");
|
||||||
} else{
|
} else {
|
||||||
setConfirmLoading(true)
|
setConfirmLoading(true);
|
||||||
postReqs(selectedGroups, config).then(() => {
|
postReqs(selectedGroups, config).then(() => {
|
||||||
setConfirmLoading(false)
|
setConfirmLoading(false);
|
||||||
setShowModal(false)
|
setShowModal(false);
|
||||||
return reload()
|
return reload();
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
return <Modal title="复制订阅" visible={showModal} confirmLoading={confirmLoading}
|
return (
|
||||||
onCancel={() => setShowModal(false)} onOk={handleOk}>
|
<Modal
|
||||||
<Select mode="multiple" onChange={(value: Array<string>) => setSelectGroups(value)}
|
title="复制订阅"
|
||||||
style={{width: '80%'}}>
|
visible={showModal}
|
||||||
{
|
confirmLoading={confirmLoading}
|
||||||
Object.keys(groups).filter(groupNumber => groupNumber !== currentGroupNumber)
|
onCancel={() => setShowModal(false)}
|
||||||
.map((groupNumber) =>
|
onOk={handleOk}
|
||||||
<Select.Option value={groupNumber} key={groupNumber}>
|
>
|
||||||
{`${groupNumber} - ${groups[groupNumber].name}`}
|
<Select
|
||||||
</Select.Option>)
|
mode="multiple"
|
||||||
}
|
onChange={(value: Array<string>) => setSelectGroups(value)}
|
||||||
</Select>
|
style={{ width: "80%" }}
|
||||||
|
>
|
||||||
|
{Object.keys(groups)
|
||||||
|
.filter((groupNumber) => groupNumber !== currentGroupNumber)
|
||||||
|
.map((groupNumber) => (
|
||||||
|
<Select.Option value={groupNumber} key={groupNumber}>
|
||||||
|
{`${groupNumber} - ${groups[groupNumber].name}`}
|
||||||
|
</Select.Option>
|
||||||
|
))}
|
||||||
|
</Select>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
interface SubscribeCardProp {
|
interface SubscribeCardProp {
|
||||||
groupNumber: string
|
groupNumber: string;
|
||||||
config: SubscribeConfig
|
config: SubscribeConfig;
|
||||||
}
|
}
|
||||||
export function SubscribeCard({groupNumber, config}: SubscribeCardProp) {
|
export function SubscribeCard({ groupNumber, config }: SubscribeCardProp) {
|
||||||
const platformConfs = useSelector(platformConfSelector)
|
const platformConfs = useSelector(platformConfSelector);
|
||||||
const [showModal, setShowModal] = useState(false)
|
const [showModal, setShowModal] = useState(false);
|
||||||
const [showEditModal, setShowEditModal] = useState(false)
|
const [showEditModal, setShowEditModal] = useState(false);
|
||||||
const platformConf = platformConfs[config.platformName] as PlatformConfig;
|
const platformConf = platformConfs[config.platformName] as PlatformConfig;
|
||||||
const dispatcher = useDispatch();
|
const dispatcher = useDispatch();
|
||||||
const groupSubscribes = useSelector(groupConfigSelector);
|
const groupSubscribes = useSelector(groupConfigSelector);
|
||||||
const reload = () => dispatcher(updateGroupSubs())
|
const reload = () => dispatcher(updateGroupSubs());
|
||||||
const handleDelete = (groupNumber: string, platformName: string, target: string) => () => {
|
const handleDelete =
|
||||||
delSubscribe(groupNumber, platformName, target).then(() => {
|
(groupNumber: string, platformName: string, target: string) => () => {
|
||||||
reload()
|
delSubscribe(groupNumber, platformName, target).then(() => {
|
||||||
})
|
reload();
|
||||||
}
|
});
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<Col span={8} key={`${config.platformName}-${config.target}`}>
|
<Col span={8} key={`${config.platformName}-${config.target}`}>
|
||||||
<Card title={`${platformConf.name} - ${config.targetName}`}
|
<Card
|
||||||
actions={[
|
title={`${platformConf.name} - ${config.targetName}`}
|
||||||
<Tooltip title="编辑">
|
actions={[
|
||||||
<EditOutlined onClick={()=>{setShowEditModal(state => !state)}}/>
|
<Tooltip title="编辑">
|
||||||
</Tooltip>,
|
<EditOutlined
|
||||||
<Tooltip title="添加到其他群">
|
onClick={() => {
|
||||||
<CopyOutlined onClick={()=>{setShowModal(state => !state)}}/>
|
setShowEditModal((state) => !state);
|
||||||
</Tooltip>,
|
}}
|
||||||
<Popconfirm title={`确定要删除 ${platformConf.name} - ${config.targetName}`}
|
/>
|
||||||
onConfirm={handleDelete(groupNumber, config.platformName, config.target || 'default')}>
|
</Tooltip>,
|
||||||
<Tooltip title="删除" ><DeleteOutlined /></Tooltip>
|
<Tooltip title="添加到其他群">
|
||||||
</Popconfirm>,
|
<CopyOutlined
|
||||||
]}>
|
onClick={() => {
|
||||||
<Form labelCol={{ span: 4 }}>
|
setShowModal((state) => !state);
|
||||||
<Form.Item label="订阅帐号">
|
}}
|
||||||
{ platformConf.hasTarget ? config.target : <Tag color="default">无帐号</Tag> }
|
/>
|
||||||
</Form.Item>
|
</Tooltip>,
|
||||||
<Form.Item label="订阅类型">
|
<Popconfirm
|
||||||
{Object.keys(platformConf.categories).length > 0 ?
|
title={`确定要删除 ${platformConf.name} - ${config.targetName}`}
|
||||||
config.cats.map((catKey: number) => (<Tag color="green" key={catKey}>{platformConf.categories[catKey]}</Tag>)) :
|
onConfirm={handleDelete(
|
||||||
<Tag color="default">不支持类型</Tag>}
|
groupNumber,
|
||||||
</Form.Item>
|
config.platformName,
|
||||||
<Form.Item label="订阅Tag">
|
config.target || "default"
|
||||||
{platformConf.enabledTag ? config.tags.length > 0 ? config.tags.map(tag => (<Tag color="green" key={tag}>{tag}</Tag>)) : (<Tag color="blue">全部标签</Tag>) :
|
)}
|
||||||
<Tag color="default">不支持Tag</Tag>}
|
>
|
||||||
</Form.Item>
|
<Tooltip title="删除">
|
||||||
</Form>
|
<DeleteOutlined />
|
||||||
</Card>
|
</Tooltip>
|
||||||
<CopyModal setShowModal={setShowModal} reload={reload} currentGroupNumber={groupNumber}
|
</Popconfirm>,
|
||||||
showModal={showModal} config={config} groups={groupSubscribes}/>
|
]}
|
||||||
<AddModal showModal={showEditModal} setShowModal={setShowEditModal}
|
>
|
||||||
groupNumber={groupNumber} refresh={reload} initVal={config}/>
|
<Form labelCol={{ span: 4 }}>
|
||||||
</Col>
|
<Form.Item label="订阅帐号">
|
||||||
)
|
{platformConf.hasTarget ? (
|
||||||
|
config.target
|
||||||
|
) : (
|
||||||
|
<Tag color="default">无帐号</Tag>
|
||||||
|
)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="订阅类型">
|
||||||
|
{Object.keys(platformConf.categories).length > 0 ? (
|
||||||
|
config.cats.map((catKey: number) => (
|
||||||
|
<Tag color="green" key={catKey}>
|
||||||
|
{platformConf.categories[catKey]}
|
||||||
|
</Tag>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<Tag color="default">不支持类型</Tag>
|
||||||
|
)}
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item label="订阅Tag">
|
||||||
|
{platformConf.enabledTag ? (
|
||||||
|
config.tags.length > 0 ? (
|
||||||
|
config.tags.map((tag) => (
|
||||||
|
<Tag color="green" key={tag}>
|
||||||
|
{tag}
|
||||||
|
</Tag>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<Tag color="blue">全部标签</Tag>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<Tag color="default">不支持Tag</Tag>
|
||||||
|
)}
|
||||||
|
</Form.Item>
|
||||||
|
</Form>
|
||||||
|
</Card>
|
||||||
|
<CopyModal
|
||||||
|
setShowModal={setShowModal}
|
||||||
|
reload={reload}
|
||||||
|
currentGroupNumber={groupNumber}
|
||||||
|
showModal={showModal}
|
||||||
|
config={config}
|
||||||
|
groups={groupSubscribes}
|
||||||
|
/>
|
||||||
|
<AddModal
|
||||||
|
showModal={showEditModal}
|
||||||
|
setShowModal={setShowEditModal}
|
||||||
|
groupNumber={groupNumber}
|
||||||
|
refresh={reload}
|
||||||
|
initVal={config}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import React from 'react';
|
import React from "react";
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from "react-dom";
|
||||||
import {Provider} from 'react-redux';
|
import { Provider } from "react-redux";
|
||||||
import App from './App';
|
import App from "./App";
|
||||||
import './index.css';
|
import "./index.css";
|
||||||
import reportWebVitals from './reportWebVitals';
|
import reportWebVitals from "./reportWebVitals";
|
||||||
import store from './store';
|
import store from "./store";
|
||||||
import {injectStore} from 'src/api/utils';
|
import { injectStore } from "src/api/utils";
|
||||||
|
|
||||||
injectStore(store);
|
injectStore(store);
|
||||||
ReactDOM.render(
|
ReactDOM.render(
|
||||||
@ -14,7 +14,7 @@ ReactDOM.render(
|
|||||||
<App />
|
<App />
|
||||||
</Provider>
|
</Provider>
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
document.getElementById('root')
|
document.getElementById("root")
|
||||||
);
|
);
|
||||||
|
|
||||||
// If you want to start measuring performance in your app, pass a function
|
// If you want to start measuring performance in your app, pass a function
|
||||||
|
@ -1,56 +1,73 @@
|
|||||||
import {Button, Collapse, Empty, Row} from 'antd';
|
import { Button, Collapse, Empty, Row } from "antd";
|
||||||
import React, {ReactElement, useEffect, useState} from "react";
|
import React, { ReactElement, useEffect, useState } from "react";
|
||||||
import {useDispatch, useSelector} from 'react-redux';
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import {AddModal} from 'src/component/addSubsModal';
|
import { AddModal } from "src/component/addSubsModal";
|
||||||
import {SubscribeCard} from 'src/component/subscribeCard';
|
import { SubscribeCard } from "src/component/subscribeCard";
|
||||||
import {groupConfigSelector, updateGroupSubs} from 'src/store/groupConfigSlice';
|
import {
|
||||||
|
groupConfigSelector,
|
||||||
|
updateGroupSubs,
|
||||||
|
} from "src/store/groupConfigSlice";
|
||||||
|
|
||||||
interface ConfigPageProp {
|
interface ConfigPageProp {
|
||||||
tab: string
|
tab: string;
|
||||||
}
|
}
|
||||||
export function ConfigPage(prop: ConfigPageProp) {
|
export function ConfigPage(prop: ConfigPageProp) {
|
||||||
const [ showModal, setShowModal ] = useState<boolean>(false);
|
const [showModal, setShowModal] = useState<boolean>(false);
|
||||||
const [ currentAddingGroupNumber, setCurrentAddingGroupNumber ] = useState('');
|
const [currentAddingGroupNumber, setCurrentAddingGroupNumber] = useState("");
|
||||||
const configData = useSelector(groupConfigSelector);
|
const configData = useSelector(groupConfigSelector);
|
||||||
const dispatcher = useDispatch();
|
const dispatcher = useDispatch();
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatcher(updateGroupSubs())
|
dispatcher(updateGroupSubs());
|
||||||
}, [prop.tab, dispatcher]);
|
}, [prop.tab, dispatcher]);
|
||||||
const clickNew = (groupNumber: string) => (e: React.MouseEvent<HTMLButtonElement>) => {
|
const clickNew =
|
||||||
setShowModal(_ => true);
|
(groupNumber: string) => (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
setCurrentAddingGroupNumber(groupNumber);
|
setShowModal((_) => true);
|
||||||
e.stopPropagation();
|
setCurrentAddingGroupNumber(groupNumber);
|
||||||
}
|
e.stopPropagation();
|
||||||
|
};
|
||||||
|
|
||||||
if (Object.keys(configData).length === 0) {
|
if (Object.keys(configData).length === 0) {
|
||||||
return <Empty />
|
return <Empty />;
|
||||||
} else {
|
} else {
|
||||||
let groups: Array<ReactElement> = [];
|
let groups: Array<ReactElement> = [];
|
||||||
for (let key of Object.keys(configData)) {
|
for (let key of Object.keys(configData)) {
|
||||||
let value = configData[key];
|
let value = configData[key];
|
||||||
groups.push(
|
groups.push(
|
||||||
<Collapse.Panel key={key} header={
|
<Collapse.Panel
|
||||||
<span>{`${key} - ${value.name}`}<Button style={{float: "right"}} onClick={clickNew(key)}>添加</Button></span>
|
key={key}
|
||||||
}>
|
header={
|
||||||
<Row gutter={[{ xs: 8, sm: 16, md: 24, lg: 32},
|
<span>
|
||||||
{ xs: 8, sm: 16, md: 24, lg: 32}]} align="middle">
|
{`${key} - ${value.name}`}
|
||||||
{value.subscribes.map((subs, idx) => <SubscribeCard key={idx}
|
<Button style={{ float: "right" }} onClick={clickNew(key)}>
|
||||||
groupNumber={key} config={subs} />)}
|
添加
|
||||||
|
</Button>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Row
|
||||||
|
gutter={[
|
||||||
|
{ xs: 8, sm: 16, md: 24, lg: 32 },
|
||||||
|
{ xs: 8, sm: 16, md: 24, lg: 32 },
|
||||||
|
]}
|
||||||
|
align="middle"
|
||||||
|
>
|
||||||
|
{value.subscribes.map((subs, idx) => (
|
||||||
|
<SubscribeCard key={idx} groupNumber={key} config={subs} />
|
||||||
|
))}
|
||||||
</Row>
|
</Row>
|
||||||
</Collapse.Panel>
|
</Collapse.Panel>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Collapse>
|
<Collapse>{groups}</Collapse>
|
||||||
{groups}
|
<AddModal
|
||||||
</Collapse>
|
groupNumber={currentAddingGroupNumber}
|
||||||
<AddModal groupNumber={currentAddingGroupNumber} showModal={showModal}
|
showModal={showModal}
|
||||||
refresh={() => dispatcher(updateGroupSubs())}
|
refresh={() => dispatcher(updateGroupSubs())}
|
||||||
setShowModal={(s: boolean) => setShowModal(_ => s)} />
|
setShowModal={(s: boolean) => setShowModal((_) => s)}
|
||||||
</div>
|
/>
|
||||||
)
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -1,37 +1,39 @@
|
|||||||
import {BugOutlined, SettingOutlined} from '@ant-design/icons';
|
import { BugOutlined, SettingOutlined } from "@ant-design/icons";
|
||||||
import {Layout, Menu} from 'antd';
|
import { Layout, Menu } from "antd";
|
||||||
import React, {useState} from "react";
|
import React, { useState } from "react";
|
||||||
import {useSelector} from 'react-redux';
|
import { useSelector } from "react-redux";
|
||||||
import {loginSelector} from 'src/store/loginSlice';
|
import { loginSelector } from "src/store/loginSlice";
|
||||||
import './admin.css';
|
import "./admin.css";
|
||||||
import {ConfigPage} from './configPage';
|
import { ConfigPage } from "./configPage";
|
||||||
|
|
||||||
export function Admin() {
|
export function Admin() {
|
||||||
const login = useSelector(loginSelector)
|
const login = useSelector(loginSelector);
|
||||||
const [ tab, changeTab ] = useState("manage");
|
const [tab, changeTab] = useState("manage");
|
||||||
return (
|
return (
|
||||||
<Layout style={{ minHeight: '100vh' }}>
|
<Layout style={{ minHeight: "100vh" }}>
|
||||||
<Layout.Sider className="layout-side">
|
<Layout.Sider className="layout-side">
|
||||||
<div className="user">
|
<div className="user"></div>
|
||||||
</div>
|
<Menu
|
||||||
<Menu mode="inline" theme="dark" defaultSelectedKeys={[tab]}
|
mode="inline"
|
||||||
onClick={({key}) => changeTab(key)}>
|
theme="dark"
|
||||||
<Menu.Item key="manage" icon={<SettingOutlined />}>订阅管理</Menu.Item>
|
defaultSelectedKeys={[tab]}
|
||||||
{ login.type === 'admin' &&
|
onClick={({ key }) => changeTab(key)}
|
||||||
<Menu.Item key="log" icon={<BugOutlined />}>查看日志</Menu.Item>
|
>
|
||||||
}
|
<Menu.Item key="manage" icon={<SettingOutlined />}>
|
||||||
</Menu>
|
订阅管理
|
||||||
</Layout.Sider>
|
</Menu.Item>
|
||||||
<Layout.Content>
|
{login.type === "admin" && (
|
||||||
<div style={{margin: '24px', background: '#fff', minHeight: '640px'}}>
|
<Menu.Item key="log" icon={<BugOutlined />}>
|
||||||
{
|
查看日志
|
||||||
tab === 'manage' ?
|
</Menu.Item>
|
||||||
<ConfigPage tab={tab}/>
|
)}
|
||||||
: null
|
</Menu>
|
||||||
}
|
</Layout.Sider>
|
||||||
</div>
|
<Layout.Content>
|
||||||
</Layout.Content>
|
<div style={{ margin: "24px", background: "#fff", minHeight: "640px" }}>
|
||||||
</Layout>
|
{tab === "manage" ? <ConfigPage tab={tab} /> : null}
|
||||||
)
|
</div>
|
||||||
|
</Layout.Content>
|
||||||
|
</Layout>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,28 +1,31 @@
|
|||||||
import React, {useEffect} from "react";
|
import React, { useEffect } from "react";
|
||||||
import {useDispatch, useSelector} from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import {useParams} from "react-router";
|
import { useParams } from "react-router";
|
||||||
import {Redirect} from 'react-router-dom';
|
import { Redirect } from "react-router-dom";
|
||||||
import {login, loginSelector} from 'src/store/loginSlice';
|
import { login, loginSelector } from "src/store/loginSlice";
|
||||||
|
|
||||||
interface AuthParam {
|
interface AuthParam {
|
||||||
code: string
|
code: string;
|
||||||
}
|
}
|
||||||
export function Auth() {
|
export function Auth() {
|
||||||
const { code } = useParams<AuthParam>();
|
const { code } = useParams<AuthParam>();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const loginState = useSelector(loginSelector)
|
const loginState = useSelector(loginSelector);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loginFun = async () => {
|
const loginFun = async () => {
|
||||||
dispatch(login(code));
|
dispatch(login(code));
|
||||||
}
|
};
|
||||||
loginFun();
|
loginFun();
|
||||||
}, [code, dispatch])
|
}, [code, dispatch]);
|
||||||
return <>
|
return (
|
||||||
{ loginState.login ?
|
<>
|
||||||
<Redirect to={{pathname: '/admin'}} /> :
|
{loginState.login ? (
|
||||||
loginState.failed ?
|
<Redirect to={{ pathname: "/admin" }} />
|
||||||
<div>登录失败,请重新获取连接</div> :
|
) : loginState.failed ? (
|
||||||
<div>Logining...</div>
|
<div>登录失败,请重新获取连接</div>
|
||||||
}
|
) : (
|
||||||
</>;
|
<div>Logining...</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { ReportHandler } from 'web-vitals';
|
import { ReportHandler } from "web-vitals";
|
||||||
|
|
||||||
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
const reportWebVitals = (onPerfEntry?: ReportHandler) => {
|
||||||
if (onPerfEntry && onPerfEntry instanceof Function) {
|
if (onPerfEntry && onPerfEntry instanceof Function) {
|
||||||
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
import("web-vitals").then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
|
||||||
getCLS(onPerfEntry);
|
getCLS(onPerfEntry);
|
||||||
getFID(onPerfEntry);
|
getFID(onPerfEntry);
|
||||||
getFCP(onPerfEntry);
|
getFCP(onPerfEntry);
|
||||||
|
@ -2,4 +2,4 @@
|
|||||||
// allows you to do things like:
|
// allows you to do things like:
|
||||||
// expect(element).toHaveTextContent(/react/i)
|
// expect(element).toHaveTextContent(/react/i)
|
||||||
// learn more: https://github.com/testing-library/jest-dom
|
// learn more: https://github.com/testing-library/jest-dom
|
||||||
import '@testing-library/jest-dom';
|
import "@testing-library/jest-dom";
|
||||||
|
@ -1,23 +1,31 @@
|
|||||||
import {CaseReducer, createAsyncThunk, createSlice, PayloadAction} from "@reduxjs/toolkit";
|
import {
|
||||||
import {getGlobalConf as getGlobalConfApi} from "src/api/config";
|
CaseReducer,
|
||||||
import {GlobalConf} from "src/utils/type";
|
createAsyncThunk,
|
||||||
import {RootState} from ".";
|
createSlice,
|
||||||
|
PayloadAction,
|
||||||
|
} from "@reduxjs/toolkit";
|
||||||
|
import { getGlobalConf as getGlobalConfApi } from "src/api/config";
|
||||||
|
import { GlobalConf } from "src/utils/type";
|
||||||
|
import { RootState } from ".";
|
||||||
|
|
||||||
const initialState: GlobalConf = {
|
const initialState: GlobalConf = {
|
||||||
platformConf: {},
|
platformConf: {},
|
||||||
loaded: false
|
loaded: false,
|
||||||
}
|
};
|
||||||
|
|
||||||
const setGlobalConf: CaseReducer<GlobalConf, PayloadAction<GlobalConf>> = (_, action) => {
|
const setGlobalConf: CaseReducer<GlobalConf, PayloadAction<GlobalConf>> = (
|
||||||
return {...action.payload, loaded: true}
|
_,
|
||||||
}
|
action
|
||||||
|
) => {
|
||||||
|
return { ...action.payload, loaded: true };
|
||||||
|
};
|
||||||
|
|
||||||
export const getGlobalConf = createAsyncThunk(
|
export const getGlobalConf = createAsyncThunk(
|
||||||
"globalConf/set",
|
"globalConf/set",
|
||||||
getGlobalConfApi,
|
getGlobalConfApi,
|
||||||
{
|
{
|
||||||
condition: (_, { getState }) => !(getState() as RootState).globalConf.loaded
|
condition: (_, { getState }) =>
|
||||||
|
!(getState() as RootState).globalConf.loaded,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -26,10 +34,11 @@ export const globalConfSlice = createSlice({
|
|||||||
initialState,
|
initialState,
|
||||||
reducers: {},
|
reducers: {},
|
||||||
extraReducers: (builder) => {
|
extraReducers: (builder) => {
|
||||||
builder.addCase(getGlobalConf.fulfilled, setGlobalConf)
|
builder.addCase(getGlobalConf.fulfilled, setGlobalConf);
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
export const platformConfSelector = (state: RootState) => state.globalConf.platformConf
|
export const platformConfSelector = (state: RootState) =>
|
||||||
|
state.globalConf.platformConf;
|
||||||
|
|
||||||
export default globalConfSlice.reducer
|
export default globalConfSlice.reducer;
|
||||||
|
@ -1,27 +1,36 @@
|
|||||||
import {CaseReducer, createAsyncThunk, createSlice, PayloadAction} from '@reduxjs/toolkit';
|
import {
|
||||||
import {SubscribeResp} from 'src/utils/type';
|
CaseReducer,
|
||||||
import {getSubscribe} from 'src/api/config';
|
createAsyncThunk,
|
||||||
import {RootState} from '.';
|
createSlice,
|
||||||
const initialState: SubscribeResp = {}
|
PayloadAction,
|
||||||
|
} from "@reduxjs/toolkit";
|
||||||
|
import { SubscribeResp } from "src/utils/type";
|
||||||
|
import { getSubscribe } from "src/api/config";
|
||||||
|
import { RootState } from ".";
|
||||||
|
const initialState: SubscribeResp = {};
|
||||||
|
|
||||||
const setSubs: CaseReducer<SubscribeResp, PayloadAction<SubscribeResp>> = (_, action) => {
|
const setSubs: CaseReducer<SubscribeResp, PayloadAction<SubscribeResp>> = (
|
||||||
return action.payload
|
_,
|
||||||
}
|
action
|
||||||
|
) => {
|
||||||
|
return action.payload;
|
||||||
|
};
|
||||||
|
|
||||||
export const updateGroupSubs = createAsyncThunk(
|
export const updateGroupSubs = createAsyncThunk(
|
||||||
"groupConfig/update", getSubscribe
|
"groupConfig/update",
|
||||||
)
|
getSubscribe
|
||||||
|
);
|
||||||
|
|
||||||
export const groupConfigSlice = createSlice({
|
export const groupConfigSlice = createSlice({
|
||||||
name: "groupConfig",
|
name: "groupConfig",
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
setSubs
|
setSubs,
|
||||||
},
|
},
|
||||||
extraReducers: (reducer) => {
|
extraReducers: (reducer) => {
|
||||||
reducer.addCase(updateGroupSubs.fulfilled, setSubs)
|
reducer.addCase(updateGroupSubs.fulfilled, setSubs);
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
export const groupConfigSelector = (state: RootState) => state.groupConfig;
|
export const groupConfigSelector = (state: RootState) => state.groupConfig;
|
||||||
export default groupConfigSlice.reducer;
|
export default groupConfigSlice.reducer;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import {TypedUseSelectorHook, useDispatch, useSelector} from "react-redux";
|
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
|
||||||
import {AppDispatch, RootState} from ".";
|
import { AppDispatch, RootState } from ".";
|
||||||
|
|
||||||
export const useAppDispacher = () => useDispatch<AppDispatch>()
|
export const useAppDispacher = () => useDispatch<AppDispatch>();
|
||||||
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
|
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import {configureStore} from "@reduxjs/toolkit";
|
import { configureStore } from "@reduxjs/toolkit";
|
||||||
import loginSlice from "./loginSlice";
|
import loginSlice from "./loginSlice";
|
||||||
import globalConfSlice from "./globalConfSlice";
|
import globalConfSlice from "./globalConfSlice";
|
||||||
import groupConfigSlice from './groupConfigSlice';
|
import groupConfigSlice from "./groupConfigSlice";
|
||||||
|
|
||||||
const store = configureStore({
|
const store = configureStore({
|
||||||
reducer: {
|
reducer: {
|
||||||
login: loginSlice,
|
login: loginSlice,
|
||||||
globalConf: globalConfSlice,
|
globalConf: globalConfSlice,
|
||||||
groupConfig: groupConfigSlice,
|
groupConfig: groupConfigSlice,
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
export default store;
|
export default store;
|
||||||
|
|
||||||
|
@ -1,108 +1,121 @@
|
|||||||
import { AnyAction, CaseReducer, createAsyncThunk, createSlice, PayloadAction, ThunkAction } from "@reduxjs/toolkit";
|
import {
|
||||||
import jwt_decode from 'jwt-decode';
|
AnyAction,
|
||||||
|
CaseReducer,
|
||||||
|
createAsyncThunk,
|
||||||
|
createSlice,
|
||||||
|
PayloadAction,
|
||||||
|
ThunkAction,
|
||||||
|
} from "@reduxjs/toolkit";
|
||||||
|
import jwt_decode from "jwt-decode";
|
||||||
import { LoginStatus, TokenResp } from "src/utils/type";
|
import { LoginStatus, TokenResp } from "src/utils/type";
|
||||||
import { auth } from "src/api/config";
|
import { auth } from "src/api/config";
|
||||||
import {RootState} from ".";
|
import { RootState } from ".";
|
||||||
|
|
||||||
const initialState: LoginStatus = {
|
const initialState: LoginStatus = {
|
||||||
login: false,
|
login: false,
|
||||||
type: '',
|
type: "",
|
||||||
name: '',
|
name: "",
|
||||||
id: '123',
|
id: "123",
|
||||||
// groups: [],
|
// groups: [],
|
||||||
token: '',
|
token: "",
|
||||||
failed: false
|
failed: false,
|
||||||
}
|
};
|
||||||
|
|
||||||
interface storedInfo {
|
interface storedInfo {
|
||||||
type: string
|
type: string;
|
||||||
name: string
|
name: string;
|
||||||
id: string
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const loginAction: CaseReducer<LoginStatus, PayloadAction<TokenResp>> = (_, action) => {
|
const loginAction: CaseReducer<LoginStatus, PayloadAction<TokenResp>> = (
|
||||||
|
_,
|
||||||
|
action
|
||||||
|
) => {
|
||||||
return {
|
return {
|
||||||
login: true,
|
login: true,
|
||||||
failed: false,
|
failed: false,
|
||||||
type: action.payload.type,
|
type: action.payload.type,
|
||||||
name: action.payload.name,
|
name: action.payload.name,
|
||||||
id: action.payload.id,
|
id: action.payload.id,
|
||||||
token: action.payload.token
|
token: action.payload.token,
|
||||||
}
|
};
|
||||||
}
|
};
|
||||||
|
|
||||||
export const login = createAsyncThunk(
|
export const login = createAsyncThunk(
|
||||||
"auth/login",
|
"auth/login",
|
||||||
async (code: string) => {
|
async (code: string) => {
|
||||||
let res = await auth(code);
|
let res = await auth(code);
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
throw Error("Login Error")
|
throw Error("Login Error");
|
||||||
} else {
|
} else {
|
||||||
localStorage.setItem('loginInfo', JSON.stringify({
|
localStorage.setItem(
|
||||||
'type': res.type,
|
"loginInfo",
|
||||||
'name': res.name,
|
JSON.stringify({
|
||||||
id: res.id,
|
type: res.type,
|
||||||
}))
|
name: res.name,
|
||||||
localStorage.setItem('token', res.token)
|
id: res.id,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
localStorage.setItem("token", res.token);
|
||||||
}
|
}
|
||||||
return res
|
return res;
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
condition: (_: string, { getState }) => {
|
condition: (_: string, { getState }) => {
|
||||||
const { login } = getState() as { login: LoginStatus }
|
const { login } = getState() as { login: LoginStatus };
|
||||||
return !login.login;
|
return !login.login;
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
)
|
);
|
||||||
|
|
||||||
|
|
||||||
export const loginSlice = createSlice({
|
export const loginSlice = createSlice({
|
||||||
name: 'auth',
|
name: "auth",
|
||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
doLogin: loginAction,
|
doLogin: loginAction,
|
||||||
doClearLogin: (state) => {
|
doClearLogin: (state) => {
|
||||||
state.login = false
|
state.login = false;
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
extraReducers: (builder) => {
|
extraReducers: (builder) => {
|
||||||
builder.addCase(login.fulfilled, loginAction);
|
builder.addCase(login.fulfilled, loginAction);
|
||||||
builder.addCase(login.rejected, (stat) => {
|
builder.addCase(login.rejected, (stat) => {
|
||||||
stat.failed = true
|
stat.failed = true;
|
||||||
})
|
});
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
export const { doLogin, doClearLogin } = loginSlice.actions
|
export const { doLogin, doClearLogin } = loginSlice.actions;
|
||||||
|
|
||||||
export const loadLoginState = (): ThunkAction<void, RootState, unknown, AnyAction> =>
|
export const loadLoginState =
|
||||||
|
(): ThunkAction<void, RootState, unknown, AnyAction> =>
|
||||||
(dispatch, getState) => {
|
(dispatch, getState) => {
|
||||||
if (getState().login.login) {
|
if (getState().login.login) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
const infoJson = localStorage.getItem('loginInfo')
|
const infoJson = localStorage.getItem("loginInfo");
|
||||||
const jwtToken = localStorage.getItem('token');
|
const jwtToken = localStorage.getItem("token");
|
||||||
if (infoJson && jwtToken) {
|
if (infoJson && jwtToken) {
|
||||||
const decodedJwt = jwt_decode(jwtToken) as { exp: number };
|
const decodedJwt = jwt_decode(jwtToken) as { exp: number };
|
||||||
if (decodedJwt.exp < Date.now() / 1000) {
|
if (decodedJwt.exp < Date.now() / 1000) {
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
const info = JSON.parse(infoJson) as storedInfo
|
const info = JSON.parse(infoJson) as storedInfo;
|
||||||
const payload: TokenResp = {
|
const payload: TokenResp = {
|
||||||
...info,
|
...info,
|
||||||
status: 200,
|
status: 200,
|
||||||
token: jwtToken,
|
token: jwtToken,
|
||||||
}
|
};
|
||||||
dispatch(doLogin(payload))
|
dispatch(doLogin(payload));
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
export const clearLoginStatus = (): ThunkAction<void, RootState, unknown, AnyAction> =>
|
export const clearLoginStatus =
|
||||||
(dispatch) => {
|
(): ThunkAction<void, RootState, unknown, AnyAction> => (dispatch) => {
|
||||||
localStorage.removeItem('loginInfo')
|
localStorage.removeItem("loginInfo");
|
||||||
localStorage.removeItem('token')
|
localStorage.removeItem("token");
|
||||||
dispatch(doClearLogin())
|
dispatch(doClearLogin());
|
||||||
}
|
};
|
||||||
export const loginSelector = (state: RootState) => state.login
|
export const loginSelector = (state: RootState) => state.login;
|
||||||
|
|
||||||
export default loginSlice.reducer
|
export default loginSlice.reducer;
|
||||||
|
@ -1,34 +1,34 @@
|
|||||||
interface QQGroup {
|
interface QQGroup {
|
||||||
id: string,
|
id: string;
|
||||||
name: string,
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface LoginStatus {
|
export interface LoginStatus {
|
||||||
login: boolean
|
login: boolean;
|
||||||
type: string
|
type: string;
|
||||||
name: string
|
name: string;
|
||||||
id: string
|
id: string;
|
||||||
// groups: Array<QQGroup>
|
// groups: Array<QQGroup>
|
||||||
token: string,
|
token: string;
|
||||||
failed: boolean,
|
failed: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LoginContextType = {
|
export type LoginContextType = {
|
||||||
login: LoginStatus
|
login: LoginStatus;
|
||||||
save: (status: LoginStatus) => void
|
save: (status: LoginStatus) => void;
|
||||||
}
|
};
|
||||||
|
|
||||||
export interface SubscribeConfig {
|
export interface SubscribeConfig {
|
||||||
platformName: string
|
platformName: string;
|
||||||
target: string
|
target: string;
|
||||||
targetName: string
|
targetName: string;
|
||||||
cats: Array<number>
|
cats: Array<number>;
|
||||||
tags: Array<string>
|
tags: Array<string>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GlobalConf {
|
export interface GlobalConf {
|
||||||
platformConf: AllPlatformConf,
|
platformConf: AllPlatformConf;
|
||||||
loaded: boolean
|
loaded: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AllPlatformConf {
|
export interface AllPlatformConf {
|
||||||
@ -36,34 +36,34 @@ export interface AllPlatformConf {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface CategoryConfig {
|
export interface CategoryConfig {
|
||||||
[idx: number]: string
|
[idx: number]: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PlatformConfig {
|
export interface PlatformConfig {
|
||||||
name: string
|
name: string;
|
||||||
categories: CategoryConfig
|
categories: CategoryConfig;
|
||||||
enabledTag: boolean,
|
enabledTag: boolean;
|
||||||
platformName: string,
|
platformName: string;
|
||||||
hasTarget: boolean
|
hasTarget: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TokenResp {
|
export interface TokenResp {
|
||||||
status: number,
|
status: number;
|
||||||
token: string,
|
token: string;
|
||||||
type: string,
|
type: string;
|
||||||
id: string
|
id: string;
|
||||||
name: string
|
name: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SubscribeGroupDetail {
|
export interface SubscribeGroupDetail {
|
||||||
name: string,
|
name: string;
|
||||||
subscribes: Array<SubscribeConfig>
|
subscribes: Array<SubscribeConfig>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SubscribeResp {
|
export interface SubscribeResp {
|
||||||
[idx: string]: SubscribeGroupDetail
|
[idx: string]: SubscribeGroupDetail;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TargetNameResp {
|
export interface TargetNameResp {
|
||||||
targetName: string
|
targetName: string;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user