This commit is contained in:
felinae98 2022-10-07 00:57:12 +08:00
parent 469dc8877b
commit 89787a187d
No known key found for this signature in database
GPG Key ID: 00C8B010587FF610
10 changed files with 392 additions and 50 deletions

View File

@ -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": {

View File

@ -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' });

View File

@ -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);

View File

@ -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}
/>
</>
);

View File

@ -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;

View 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;
}

View 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;

View File

@ -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>;
}

View File

@ -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);

View File

@ -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>