mirror of
https://github.com/suyiiyii/nonebot-bison.git
synced 2025-06-03 18:06:13 +08:00
update
This commit is contained in:
parent
469dc8877b
commit
89787a187d
@ -6,6 +6,8 @@
|
||||
},
|
||||
"extends": [
|
||||
"plugin:react/recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"airbnb"
|
||||
],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
@ -25,7 +27,9 @@
|
||||
"no-use-before-define": "off",
|
||||
"@typescript-eslint/no-use-before-define": ["error"],
|
||||
"import/extensions": ["error", "ignorePackages", {"ts": "never", "tsx": "never"}],
|
||||
"no-param-reassign": ["error", { "props": false }]
|
||||
"no-param-reassign": ["error", { "props": false }],
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": ["error"]
|
||||
},
|
||||
"settings": {
|
||||
"import/resolver": {
|
||||
|
@ -6,6 +6,7 @@ import Auth from './features/auth/Auth';
|
||||
import { loadGlobalConf, selectGlobalConfLoaded } from './features/globalConf/globalConfSlice';
|
||||
import GroupManager from './features/subsribeConfigManager/GroupManager';
|
||||
import SubscribeManager from './features/subsribeConfigManager/SubscribeManager';
|
||||
import WeightConfig from './features/weightConfig/WeightManager';
|
||||
import Home from './pages/Home';
|
||||
import Unauthed from './pages/Unauthed';
|
||||
|
||||
@ -41,6 +42,10 @@ function App() {
|
||||
path: 'groups/:groupNumber',
|
||||
element: <SubscribeManager />,
|
||||
},
|
||||
{
|
||||
path: 'weight',
|
||||
element: <WeightConfig />,
|
||||
},
|
||||
],
|
||||
},
|
||||
], { basename: '/bison' });
|
||||
|
@ -15,6 +15,7 @@ import storage from 'redux-persist/lib/storage';
|
||||
import authReducer from '../features/auth/authSlice';
|
||||
import globalConfReducer from '../features/globalConf/globalConfSlice';
|
||||
import { subscribeApi } from '../features/subsribeConfigManager/subscribeConfigSlice';
|
||||
import { targetNameApi } from '../features/targetName/targetNameSlice';
|
||||
import { weightApi } from '../features/weightConfig/weightConfigSlice';
|
||||
|
||||
const rootReducer = combineReducers({
|
||||
@ -22,6 +23,7 @@ const rootReducer = combineReducers({
|
||||
globalConf: globalConfReducer,
|
||||
[subscribeApi.reducerPath]: subscribeApi.reducer,
|
||||
[weightApi.reducerPath]: weightApi.reducer,
|
||||
[targetNameApi.reducerPath]: targetNameApi.reducer,
|
||||
});
|
||||
|
||||
const persistConfig = {
|
||||
@ -40,7 +42,8 @@ export const store = configureStore({
|
||||
},
|
||||
})
|
||||
.concat(subscribeApi.middleware)
|
||||
.concat(weightApi.middleware),
|
||||
.concat(weightApi.middleware)
|
||||
.concat(targetNameApi.middleware),
|
||||
});
|
||||
|
||||
export const persistor = persistStore(store);
|
||||
|
@ -1,18 +1,34 @@
|
||||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
Button, Empty, Space, Table, Tag,
|
||||
Button, Empty, Message, Popconfirm, Space, Table, Tag, Typography,
|
||||
} from '@arco-design/web-react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { useGetSubsQuery } from './subscribeConfigSlice';
|
||||
import { useDeleteSubMutation, useGetSubsQuery } from './subscribeConfigSlice';
|
||||
import { useAppSelector } from '../../app/hooks';
|
||||
import { selectPlatformConf } from '../globalConf/globalConfSlice';
|
||||
import { SubscribeConfig } from '../../utils/type';
|
||||
import SubscribeModal from './SubscribeModal';
|
||||
|
||||
export default function SubscribeManager() {
|
||||
const { data: subs } = useGetSubsQuery();
|
||||
const [deleteSub, { isLoading: deleteIsLoading }] = useDeleteSubMutation();
|
||||
const { groupNumber } = useParams();
|
||||
const platformConf = useAppSelector(selectPlatformConf);
|
||||
|
||||
const isLoading = deleteIsLoading;
|
||||
const [showModal, setShowModal] = useState(false);
|
||||
const [formInitVal, setFormInitVal] = useState(null as SubscribeConfig | null);
|
||||
|
||||
const handleNewSub = () => {
|
||||
setFormInitVal(null);
|
||||
setShowModal(true);
|
||||
};
|
||||
|
||||
const handleEdit = (sub: SubscribeConfig) => () => {
|
||||
setFormInitVal(sub);
|
||||
setShowModal(true);
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: '平台名称',
|
||||
@ -59,9 +75,20 @@ export default function SubscribeManager() {
|
||||
dataIndex: 'op',
|
||||
render: (_: any, record: SubscribeConfig) => (
|
||||
<Space>
|
||||
<Button type="text">编辑</Button>
|
||||
<Button type="text" status="success">复制</Button>
|
||||
<Button type="text" status="danger" onClick={() => { console.log(record); }}>删除</Button>
|
||||
<Button type="text" onClick={handleEdit(record)}>编辑</Button>
|
||||
<Button type="text" status="success" onClick={() => Message.error('懒得写了')}>复制</Button>
|
||||
<Popconfirm
|
||||
title={`确认删除订阅 ${record.targetName} ?`}
|
||||
onOk={() => {
|
||||
deleteSub({
|
||||
groupNumber: parseInt(groupNumber!, 10),
|
||||
target: record.target,
|
||||
platformName: record.platformName,
|
||||
});
|
||||
}}
|
||||
>
|
||||
<Button type="text" status="danger">删除</Button>
|
||||
</Popconfirm>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
@ -71,14 +98,21 @@ export default function SubscribeManager() {
|
||||
return (
|
||||
<>
|
||||
<span>
|
||||
{subs[groupNumber].name}
|
||||
{groupNumber}
|
||||
<Typography.Title heading={3}>{subs[groupNumber].name}</Typography.Title>
|
||||
<Typography.Text type="secondary">{groupNumber}</Typography.Text>
|
||||
</span>
|
||||
<Button style={{ width: '90px', margin: '20px' }} type="primary">添加</Button>
|
||||
<Button style={{ width: '90px', margin: '20px 10px' }} type="primary" onClick={handleNewSub}>添加</Button>
|
||||
<Table
|
||||
columns={columns}
|
||||
data={subs[groupNumber].subscribes}
|
||||
rowKey={(record: SubscribeConfig) => `${record.platformName}-${record.target}`}
|
||||
loading={isLoading}
|
||||
/>
|
||||
<SubscribeModal
|
||||
visible={showModal}
|
||||
setVisible={setShowModal}
|
||||
groupNumber={groupNumber}
|
||||
initval={formInitVal}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
@ -0,0 +1,241 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
Form, Input, InputTag, Modal, Select, Space, Tag,
|
||||
} from '@arco-design/web-react';
|
||||
import useForm from '@arco-design/web-react/es/Form/useForm';
|
||||
import { IconInfoCircle } from '@arco-design/web-react/icon';
|
||||
import { useAppSelector } from '../../app/hooks';
|
||||
import { selectPlatformConf } from '../globalConf/globalConfSlice';
|
||||
import { CategoryConfig, SubscribeConfig } from '../../utils/type';
|
||||
import getTargetName from '../targetName/targetNameReq';
|
||||
import { useUpdateSubMutation, useNewSubMutation } from './subscribeConfigSlice';
|
||||
|
||||
function SubscribeTag({
|
||||
value, onChange, disabled,
|
||||
}: {
|
||||
value?: string[];
|
||||
onChange?: (arg0: string[]) => void;
|
||||
disabled?: boolean;
|
||||
}) {
|
||||
const [valueState, setValueState] = useState(value || []);
|
||||
const handleSetValue = (newVal: string[]) => {
|
||||
setValueState(newVal);
|
||||
if (onChange) {
|
||||
onChange(newVal);
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
if (value) {
|
||||
setValueState(value);
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
if (disabled) {
|
||||
return <Tag color="gray">不支持标签</Tag>;
|
||||
}
|
||||
return (
|
||||
<Space>
|
||||
{ valueState.length === 0 && <Tag color="green">全部标签</Tag> }
|
||||
<InputTag
|
||||
allowClear
|
||||
placeholder="添加标签"
|
||||
value={value}
|
||||
onChange={handleSetValue}
|
||||
/>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
SubscribeTag.defaultProps = {
|
||||
value: [],
|
||||
onChange: null,
|
||||
disabled: false,
|
||||
};
|
||||
|
||||
interface SubscribeModalProp {
|
||||
visible: boolean;
|
||||
setVisible: (arg0: boolean) => void;
|
||||
groupNumber: string;
|
||||
initval?: SubscribeConfig | null;
|
||||
}
|
||||
|
||||
function SubscribeModal({
|
||||
visible, setVisible, groupNumber, initval,
|
||||
}: SubscribeModalProp) {
|
||||
const [form] = useForm();
|
||||
const [confirmLoading, setConfirmLoading] = useState(false);
|
||||
const platformConf = useAppSelector(selectPlatformConf);
|
||||
const [updateSub] = useUpdateSubMutation();
|
||||
const [newSub] = useNewSubMutation();
|
||||
|
||||
const onSubmit = () => {
|
||||
form.validate().then((value: SubscribeConfig) => {
|
||||
const newVal = { ...value };
|
||||
if (typeof newVal.tags !== 'object') {
|
||||
newVal.tags = [];
|
||||
}
|
||||
if (typeof newVal.cats !== 'object') {
|
||||
newVal.cats = [];
|
||||
}
|
||||
if (newVal.target === '') {
|
||||
newVal.target = 'default';
|
||||
}
|
||||
let postPromise: Promise<any>;
|
||||
if (initval) {
|
||||
postPromise = updateSub({
|
||||
groupNumber: parseInt(groupNumber, 10),
|
||||
sub: newVal,
|
||||
});
|
||||
} else {
|
||||
postPromise = newSub({
|
||||
groupNumber: parseInt(groupNumber, 10),
|
||||
sub: newVal,
|
||||
});
|
||||
}
|
||||
setConfirmLoading(true);
|
||||
postPromise.then(() => {
|
||||
setConfirmLoading(false);
|
||||
setVisible(false);
|
||||
form.clearFields();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const [hasTarget, setHasTarget] = useState(false);
|
||||
const [categories, setCategories] = useState({} as CategoryConfig);
|
||||
const [enableTags, setEnableTags] = useState(false);
|
||||
|
||||
const setPlatformStates = (platform: string) => {
|
||||
setHasTarget(platformConf[platform].hasTarget);
|
||||
setCategories(platformConf[platform].categories);
|
||||
setEnableTags(platformConf[platform].enabledTag);
|
||||
};
|
||||
|
||||
const handlePlatformSelected = (platform: string) => {
|
||||
setPlatformStates(platform);
|
||||
form.setFieldValue('cats', []);
|
||||
if (!platformConf[platform].hasTarget) {
|
||||
getTargetName(platform, 'default').then((res) => {
|
||||
form.setFieldsValue({
|
||||
targetName: res,
|
||||
target: '',
|
||||
});
|
||||
});
|
||||
} else {
|
||||
form.setFieldsValue({
|
||||
targetName: '',
|
||||
target: '',
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (initval) {
|
||||
const { platformName } = initval;
|
||||
setPlatformStates(platformName);
|
||||
form.setFieldsValue(initval);
|
||||
} else {
|
||||
form.clearFields();
|
||||
}
|
||||
}, [initval, form, platformConf]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title="编辑订阅"
|
||||
visible={visible}
|
||||
onCancel={() => setVisible(false)}
|
||||
confirmLoading={confirmLoading}
|
||||
onOk={onSubmit}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
>
|
||||
<Form.Item label="平台" field="platformName">
|
||||
<Select placeholder="平台" onChange={handlePlatformSelected}>
|
||||
{ Object.keys(platformConf).map(
|
||||
(platformName: string) => (
|
||||
<Select.Option value={platformName} key={platformName}>
|
||||
{platformConf[platformName].name}
|
||||
</Select.Option>
|
||||
),
|
||||
) }
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="帐号"
|
||||
field="target"
|
||||
rules={[
|
||||
{ required: hasTarget, message: '请输入账号' },
|
||||
{
|
||||
validator: (value, callback) => new Promise<void>((resolve) => {
|
||||
getTargetName(form.getFieldValue('platformName'), value)
|
||||
.then((res) => {
|
||||
if (res) {
|
||||
form.setFieldsValue({
|
||||
targetName: res,
|
||||
});
|
||||
resolve();
|
||||
} else {
|
||||
form.setFieldsValue({
|
||||
targetName: '',
|
||||
});
|
||||
callback('账号不正确,请重新检查账号');
|
||||
resolve();
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
callback('服务器错误,请稍后再试');
|
||||
resolve();
|
||||
});
|
||||
}),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
disabled={!hasTarget}
|
||||
suffix={<IconInfoCircle />}
|
||||
placeholder={hasTarget ? '获取方式见文档' : '此平台不需要账号'}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="帐号名称" field="targetName">
|
||||
<Input disabled />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label="订阅分类"
|
||||
field="cats"
|
||||
rules={[
|
||||
{
|
||||
required: Object.keys(categories).length > 0,
|
||||
message: '请至少选择一个分类进行订阅',
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Select
|
||||
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, 10)}>
|
||||
{ categories[parseInt(indexStr, 10)] }
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
|
||||
</Form.Item>
|
||||
<Form.Item label="标签" field="tags">
|
||||
<SubscribeTag disabled={!enableTags} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
SubscribeModal.defaultProps = {
|
||||
initval: null,
|
||||
};
|
||||
export default SubscribeModal;
|
15
admin-frontend/src/features/targetName/targetNameReq.ts
Normal file
15
admin-frontend/src/features/targetName/targetNameReq.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { RootState, store } from '../../app/store';
|
||||
import { baseUrl } from '../../utils/urls';
|
||||
|
||||
export default async function getTargetName(platformName: string, target: string) {
|
||||
const url = `${baseUrl}target_name?platformName=${platformName}&target=${target}`;
|
||||
const state = store.getState() as RootState;
|
||||
const authToken = state.auth.token;
|
||||
const res = await fetch(url, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${authToken}`,
|
||||
},
|
||||
});
|
||||
const resObj = await res.json();
|
||||
return resObj.targetName as string;
|
||||
}
|
14
admin-frontend/src/features/targetName/targetNameSlice.ts
Normal file
14
admin-frontend/src/features/targetName/targetNameSlice.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { createApi } from '@reduxjs/toolkit/query/react';
|
||||
import baseQueryWithAuth from '../auth/authQuery';
|
||||
|
||||
export const targetNameApi = createApi({
|
||||
reducerPath: 'targetName',
|
||||
baseQuery: baseQueryWithAuth,
|
||||
endpoints: (builder) => ({
|
||||
getTargetName: builder.query<{targetName: string}, {target: string; platformName: string}>({
|
||||
query: () => '/target_name',
|
||||
}),
|
||||
}),
|
||||
});
|
||||
|
||||
export const { useGetTargetNameQuery } = targetNameApi;
|
@ -1,28 +1,32 @@
|
||||
import React from 'react';
|
||||
import { WeightConfig } from '../../utils/type';
|
||||
import { useGetWeightQuery, useUpdateWeightMutation } from './weightConfigSlice';
|
||||
// import { WeightConfig } from '../../utils/type';
|
||||
// import { useGetWeightQuery, useUpdateWeightMutation } from './weightConfigSlice';
|
||||
//
|
||||
// export default function WeightManager() {
|
||||
// const { data: weight } = useGetWeightQuery();
|
||||
// const [updateWeight] = useUpdateWeightMutation();
|
||||
//
|
||||
// const doUpdate = () => {
|
||||
// const weightConfig: WeightConfig = {
|
||||
// default: 20,
|
||||
// time_config: [
|
||||
// {
|
||||
// start_time: '01:00',
|
||||
// end_time: '02:00',
|
||||
// weight: 50,
|
||||
// },
|
||||
// ],
|
||||
// };
|
||||
// updateWeight({ weight: weightConfig, platform_name: 'weibo', target: '' });
|
||||
// };
|
||||
// return (
|
||||
// <>
|
||||
// <div>{weight && JSON.stringify(weight)}</div>
|
||||
// <button type="button" onClick={doUpdate}> 123</button>
|
||||
// </>
|
||||
// );
|
||||
// }
|
||||
|
||||
export default function WeightManager() {
|
||||
const { data: weight } = useGetWeightQuery();
|
||||
const [updateWeight] = useUpdateWeightMutation();
|
||||
|
||||
const doUpdate = () => {
|
||||
const weightConfig: WeightConfig = {
|
||||
default: 20,
|
||||
time_config: [
|
||||
{
|
||||
start_time: '01:00',
|
||||
end_time: '02:00',
|
||||
weight: 50,
|
||||
},
|
||||
],
|
||||
};
|
||||
updateWeight({ weight: weightConfig, platform_name: 'weibo', target: '' });
|
||||
};
|
||||
return (
|
||||
<>
|
||||
<div>{weight && JSON.stringify(weight)}</div>
|
||||
<button type="button" onClick={doUpdate}> 123</button>
|
||||
</>
|
||||
);
|
||||
export default function WeightConfig() {
|
||||
return <div>下个版本再写</div>;
|
||||
}
|
||||
|
@ -7,9 +7,17 @@
|
||||
.layout-collapse-demo .arco-layout-header .logo {
|
||||
height: 32px;
|
||||
margin: 12px 8px;
|
||||
width: 150px;
|
||||
background: var(--color-fill-2);
|
||||
}
|
||||
|
||||
.layout-collapse-demo .arco-layout-header span {
|
||||
height: 100%;
|
||||
line-height: 100%;
|
||||
font-size: 20px;
|
||||
margin: 0 20px;
|
||||
}
|
||||
|
||||
.layout-collapse-demo .arco-layout-content .arco-layout-footer,
|
||||
.layout-collapse-demo .arco-layout-content .arco-layout-content {
|
||||
color: var(--color-white);
|
||||
|
@ -4,15 +4,15 @@ import { IconRobot, IconDashboard } from '@arco-design/web-react/icon';
|
||||
import './Home.css';
|
||||
// import SubscribeManager from '../features/subsribeConfigManager/SubscribeManager';
|
||||
import {
|
||||
Link, Outlet, useLocation, useNavigate,
|
||||
Link, Navigate, Outlet, useLocation, useNavigate,
|
||||
} from 'react-router-dom';
|
||||
|
||||
export function homeLoader() {
|
||||
}
|
||||
import { useAppSelector } from '../app/hooks';
|
||||
import { selectIsLogin } from '../features/auth/authSlice';
|
||||
|
||||
export default function Home() {
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const isLogin = useAppSelector(selectIsLogin);
|
||||
|
||||
const path = location.pathname;
|
||||
useEffect(() => {
|
||||
@ -20,13 +20,12 @@ export default function Home() {
|
||||
navigate('/home/groups');
|
||||
}
|
||||
|
||||
if (path !== '/home/groups' && !path.startsWith('/home/groups/')) {
|
||||
console.log(path);
|
||||
if (path !== '/home/groups' && !path.startsWith('/home/groups/') && path !== '/home/weight') {
|
||||
navigate('/home/groups');
|
||||
}
|
||||
}, [path]);
|
||||
|
||||
let currentKey: string = '';
|
||||
let currentKey = '';
|
||||
if (path === '/home/groups') {
|
||||
currentKey = 'groups';
|
||||
} else if (path.startsWith('/home/groups/')) {
|
||||
@ -38,14 +37,18 @@ export default function Home() {
|
||||
const handleTabSelect = (tab: string) => {
|
||||
changeSelectTab(tab);
|
||||
if (tab === 'groups') {
|
||||
navigate('/home/navigate');
|
||||
navigate('/home/groups');
|
||||
} else if (tab === 'weight') {
|
||||
navigate('/home/weight');
|
||||
}
|
||||
};
|
||||
|
||||
if (!isLogin) {
|
||||
return <Navigate to="/unauthed" />;
|
||||
}
|
||||
|
||||
let breadcrumbContent: ReactNode;
|
||||
if (selectedTab === 'groups') {
|
||||
if (path === '/home/groups') {
|
||||
breadcrumbContent = (
|
||||
<Breadcrumb style={{ margin: '16px 0' }}>
|
||||
<Breadcrumb.Item>
|
||||
@ -54,17 +57,26 @@ export default function Home() {
|
||||
</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
);
|
||||
} else if (selectedTab === 'subs') {
|
||||
} else if (path.startsWith('/home/groups/')) {
|
||||
breadcrumbContent = (
|
||||
<Breadcrumb style={{ margin: '16px 0' }}>
|
||||
<Breadcrumb.Item>
|
||||
<Link to="/home/groups">
|
||||
<IconRobot />
|
||||
订阅管理
|
||||
<IconDashboard />
|
||||
调度权重
|
||||
</Link>
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Item>
|
||||
groupman
|
||||
群管理
|
||||
</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
);
|
||||
} else if (path === '/home/weight') {
|
||||
breadcrumbContent = (
|
||||
<Breadcrumb style={{ margin: '16px 0' }}>
|
||||
<Breadcrumb.Item>
|
||||
<IconDashboard />
|
||||
调度权重
|
||||
</Breadcrumb.Item>
|
||||
</Breadcrumb>
|
||||
);
|
||||
@ -72,7 +84,9 @@ export default function Home() {
|
||||
return (
|
||||
<Layout className="layout-collapse-demo">
|
||||
<Layout.Header>
|
||||
<div className="logo" />
|
||||
<span>
|
||||
Nonebot Bison
|
||||
</span>
|
||||
</Layout.Header>
|
||||
<Layout className="layout-collapse-demo">
|
||||
<Layout.Sider>
|
||||
|
Loading…
x
Reference in New Issue
Block a user